#include <GL/glew.h>

#include "renderer.h"

#include "utils.h"
#include "nodetypes.h"
#include "glenums.h"
#include "matrix4.h"
#include "pfm.h"

#include <iostream>
#include <sstream>
#include <fstream>

#include <tr1/unordered_set>
#include <tr1/functional>

#include "SOILjoe/SOIL.h"
#include <assimp/assimp.h>
#include <assimp/aiPostProcess.h>
#include <assimp/aiScene.h>
#include <IL/il.h>

#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define ERROR_CHECK checkForOpenGLErrors(std::string(__PRETTY_FUNCTION__)+":"+__FILE__+":"+TOSTRING(__LINE__))

const std::string & REQUIRED_GL_VERSION = "GL_VERSION_3_2";
const GLEnums GLEnumHelper;

Renderer::Renderer() : width(0), height(0), logToWindow(true)
{
	// This is needed to force glew to probe supported OpenGL extensions to find
	// the actual level of OpenGL support, not just the spec that the driver
	// reports.
	//glewExperimental = GL_TRUE;
}

// writes errors to the log
// returns true on error
bool Renderer::checkForOpenGLErrors(std::string activity_description)
{
#if defined(WIN32) || defined(_WIN32) || defined (__WIN32) || defined(__WIN32__) \
	|| defined (_WIN64) || defined(__CYGWIN__) || defined(__MINGW32__)
	//disable error checking because intel has many driver chipsets out with non-compliant OpenGL drivers
#else
	GLenum gl_error = glGetError();
	if (gl_error != GL_NO_ERROR)
	{
		const GLubyte *err_string = gluErrorString(gl_error);
		logError(std::string("OpenGL error \"")+tostr(err_string)+"\" during: "+activity_description);
		return true;
	}
#endif
	return false;
}

const char * getIlErrorString(ILenum err)
{
#define X(x) case x: return #x;
    switch (err)
    {
        X(IL_NO_ERROR)
        X(IL_INVALID_ENUM)
        X(IL_OUT_OF_MEMORY)
        X(IL_FORMAT_NOT_SUPPORTED)
        X(IL_INTERNAL_ERROR)
        X(IL_INVALID_VALUE)
        X(IL_ILLEGAL_OPERATION)
        X(IL_ILLEGAL_FILE_VALUE)
        X(IL_INVALID_FILE_HEADER)
        X(IL_INVALID_PARAM)
        X(IL_COULD_NOT_OPEN_FILE)
        X(IL_INVALID_EXTENSION)
        X(IL_FILE_ALREADY_EXISTS)
        X(IL_OUT_FORMAT_SAME)
        X(IL_STACK_OVERFLOW)
        X(IL_STACK_UNDERFLOW)
        X(IL_INVALID_CONVERSION)
        X(IL_BAD_DIMENSIONS)
        X(IL_FILE_READ_ERROR)

        X(IL_LIB_GIF_ERROR)
        X(IL_LIB_JPEG_ERROR)
        X(IL_LIB_PNG_ERROR)
        X(IL_LIB_TIFF_ERROR)
        X(IL_LIB_MNG_ERROR)
        X(IL_LIB_JP2_ERROR)
        X(IL_LIB_EXR_ERROR)
        X(IL_UNKNOWN_ERROR)

        default:
        return "unknown error";
    }
#undef X
}

void wrapper_glEnablei(GLenum target, GLuint index)
{
	if (!glEnablei)
		glEnable(target);
	else
		glEnablei(target, index);
}

void wrapper_glDisablei(GLenum target, GLuint index)
{
	if (!glDisablei)
		glDisable(target);
	else
		glDisablei(target, index);
}

bool Renderer::initialize()
{
	logOutput(std::string("GL Renderer: ")+tostr(glGetString(GL_RENDERER)));
	logOutput(std::string("GL Vendor: ")+tostr(glGetString(GL_VENDOR)));
	logOutput(std::string("GL Version: ")+tostr(glGetString(GL_VERSION)));
	
	GLenum glew_err = glewInit();
	if (glew_err != GLEW_OK)
	{
		logError(std::string("GLEW failed to initialize: ")+tostr(glewGetErrorString(glew_err)));
		return false;
	}
	else
	{
		logOutput(std::string("Initialized GLEW ")+tostr(glewGetString(GLEW_VERSION)));
	}

    ilInit();
	
	// check through all OpenGL versions to determine the highest supported OpenGL version
	std::ifstream versionfile("glversions.txt");
	supportedGlVersion = "unknown";
	bool supportsRequiredVersion = false;
	while (versionfile)
	{
		std::string version;
		versionfile >> version;
		if (!version.empty() && glewIsSupported(version.c_str()))
		{
			supportedGlVersion = version;
			if (version == REQUIRED_GL_VERSION)
				supportsRequiredVersion = true;
		}
	}
	if (glewExperimental)
		logOutput(std::string("Probed GL version: ")+supportedGlVersion);
	else
		logOutput(std::string("GL version: ")+supportedGlVersion);
	
	if (!supportsRequiredVersion)
	{
		logError(std::string("Your card does not support ")+REQUIRED_GL_VERSION);
		return false;
	}
	
	return (!ERROR_CHECK);
}

const std::string & Renderer::getRequiredGlVersion() const
{
	return REQUIRED_GL_VERSION;
}

typedef std::tr1::unordered_set <const SceneNode *> NodeSet;

void getAddedDeleted(const std::vector <const SceneNode *> & children,
					 const Renderer::SceneMapping & mapping,
					 NodeSet & added,
					 NodeSet & deleted)
{
	for (Renderer::SceneMapping::const_iterator i = mapping.begin(); i != mapping.end(); i++)
		deleted.insert(i->first);
	for (std::vector <const SceneNode *>::const_iterator i = children.begin(); i != children.end(); i++)
	{
		deleted.erase(*i);
		if (mapping.find(*i) == mapping.end())
			added.insert(*i);
	}
}

void Renderer::deleteTexture(RenderTexture & tex)
{
	if (tex.handle != 0)
	{
		logOutput(std::string("Unloaded ")+(tex.path.empty() ? "render target" : ("texture "+tex.path))+" handle "+tostr(tex.handle));
		glDeleteTextures(1, &tex.handle);ERROR_CHECK;
		tex.handle = 0;
	}
}

GLenum toIntegerFormat(GLenum format)
{
	std::string formatstr = GLEnumHelper.getEnum(format);
	std::string baseformatstr = formatstr.substr(0,formatstr.find_first_of("1234567890"));
	return GLEnumHelper.getEnum(baseformatstr+"_INTEGER");
}

void Renderer::updateTexture(std::pair<RenderTexture &, const SceneNode *> args)
{
	RenderTexture & tex = args.first;
	const SceneNode * node = args.second;
	
	tex.nodeName = node->getName();

	if (node->getType() == NodeTypes::getType(NodeTypes::TEXTURE)) // texture loaded from disk
	{
		std::string newPath;
		node->getField("path", newPath);
		
		FileModificationTime newModTime;
		if (!newPath.empty())
			newModTime = FileModificationTime(newPath);
		
		// update any time any fields change
		if (node->getFields() != tex.fieldCache ||
			(!newPath.empty() && tex.modTime != newModTime))
		{
			tex.fieldCache = node->getFields();
			tex.modTime = newModTime;
			
			bool srgb(false);
			node->getField("SRGB", srgb);

            bool cubemap(false);
			node->getField("cubemap (EWUDNS)", cubemap);
			
			bool genMipmaps(false);
			node->getField("generate mipmaps", genMipmaps);
			
			if (!newPath.empty())
			{
				deleteTexture(tex);

                // try OpenIL first
                bool openil = ilLoadImage(newPath.c_str());
                
                ILenum ilerror = ilGetError();
                int errorcount = 0;
                while (ilerror != IL_NO_ERROR && errorcount < 100)
                {
                    logError(std::string("OpenIL error: ") + getIlErrorString(ilerror));
                    ilerror = ilGetError();
                    errorcount++;
                }

                // openil supports native cubemaps (dds) but I don't support decoding EWUDNS via the openil path
                if (openil && !cubemap)
                {
					logOutput(std::string("Using OpenIL to load texture ")+newPath);

                    // attempt to re-use texture handle, create it if it doesn't exist
                    if (tex.handle == 0)
                    {
                        glGenTextures(1, &tex.handle);
                        ERROR_CHECK;
                    }

                    // determine target
                    if (ilGetInteger(IL_NUM_FACES) == 6)
                    {
                        tex.target = GL_TEXTURE_CUBE_MAP;
                    }
                    else
                    {
                        if (ilGetInteger(IL_IMAGE_DEPTH) > 1)
                        {
                            tex.target = GL_TEXTURE_3D;
                        }
                        else if (ilGetInteger(IL_IMAGE_HEIGHT) > 1)
                        {
                            tex.target = GL_TEXTURE_2D;
                        }
                        else
                        {
                            tex.target = GL_TEXTURE_1D;
                        }
                    }

                    // bind the texture id
                    glBindTexture(tex.target, tex.handle);
                    ERROR_CHECK;

                    // determine the internal format
                    int internalFormat = ilGetInteger(IL_IMAGE_FORMAT);
                    int componentType = ilGetInteger(IL_IMAGE_TYPE);
                    if (srgb)
                    {
                        if (internalFormat == GL_RGB && componentType == GL_UNSIGNED_BYTE)
                            internalFormat = GL_SRGB;
                        else if (internalFormat == GL_RGBA && componentType == GL_UNSIGNED_BYTE)
                            internalFormat = GL_SRGB_ALPHA;
                        else
                        {
                            logError("SRGB is not supported for this format: " + GLEnumHelper.getEnumOrDefault(internalFormat, tostr(internalFormat)));
                        }
                    }
                    if (componentType == GL_FLOAT)
                    {
                        if (internalFormat == GL_RGB)
                            internalFormat = GL_RGB32F;
                        else if (internalFormat == GL_RGBA)
                            internalFormat = GL_RGBA32F;
                    }

                    // upload the texture
                    switch (tex.target)
                    {
                        case GL_TEXTURE_CUBE_MAP:
                        {
                            // TODO
                            logError("OpenIL cubemap loading is not yet implemented");
                        }
                        break;

                        case GL_TEXTURE_3D:
                        {
                            if (ilGetInteger(IL_NUM_MIPMAPS) > 1)
                            {
                                logError("OpenIL mipmap loading is not yet implemented for 3D textures");
                            }

                            const int level = 0;
                            const int border = 0;

                            glTexImage3D(
                                tex.target,
                                level,
                                internalFormat,
                                ilGetInteger(IL_IMAGE_WIDTH),
                                ilGetInteger(IL_IMAGE_HEIGHT),
                                ilGetInteger(IL_IMAGE_DEPTH),
                                border,
                                ilGetInteger(IL_IMAGE_FORMAT),
                                ilGetInteger(IL_IMAGE_TYPE),
                                ilGetData());
                        }
                        break;

                        case GL_TEXTURE_2D:
                        {
                            if (ilGetInteger(IL_NUM_MIPMAPS) > 1)
                            {
                                logError("OpenIL mipmap loading is not yet implemented");
                            }

                            const int level = 0;
                            const int border = 0;

                            glTexImage2D(
                                tex.target,
                                level,
                                internalFormat,
                                ilGetInteger(IL_IMAGE_WIDTH),
                                ilGetInteger(IL_IMAGE_HEIGHT),
                                border,
                                ilGetInteger(IL_IMAGE_FORMAT),
                                ilGetInteger(IL_IMAGE_TYPE),
                                ilGetData());
                        }
                        break;

                        case GL_TEXTURE_1D:
                        {
                            if (ilGetInteger(IL_NUM_MIPMAPS) > 1)
                            {
                                logError("OpenIL mipmap loading is not yet implemented");
                            }

                            const int level = 0;
                            const int border = 0;

                            glTexImage1D(
                                tex.target,
                                level,
                                internalFormat,
                                ilGetInteger(IL_IMAGE_WIDTH),
                                border,
                                ilGetInteger(IL_IMAGE_FORMAT),
                                ilGetInteger(IL_IMAGE_TYPE),
                                ilGetData());
                        }
                        break;
                    }
                    ERROR_CHECK;

                    logOutput(std::string("Texture format ") + 
                        GLEnumHelper.getEnumOrDefault(ilGetInteger(IL_IMAGE_FORMAT), tostr(ilGetInteger(IL_IMAGE_FORMAT))) +
                        " type " +
                        GLEnumHelper.getEnumOrDefault(ilGetInteger(IL_IMAGE_TYPE), tostr(ilGetInteger(IL_IMAGE_TYPE))));

                    if (genMipmaps)
                    {
						glGenerateMipmap(tex.target);ERROR_CHECK;
                    }
                }
                else
                {
                    // backup path: tweaked SOIL
					logOutput(std::string("Using SOIL to load texture ")+newPath);

                    if (cubemap)
                    {
                        tex.handle = SOIL_load_OGL_single_cubemap(newPath.c_str(), "EWUDNS", SOIL_LOAD_AUTO, tex.handle, 
                                                           SOIL_FLAG_MIPMAPS | 
                                                           SOIL_FLAG_DDS_LOAD_DIRECT | 
                                                           (srgb ? SOIL_FLAG_SRGB : 0) |
                                                           0);
                                                           ERROR_CHECK;
                        tex.target = GL_TEXTURE_CUBE_MAP;
                    }
                    else if (hasExtension(newPath, ".pfm"))
                    {
                        const vector2d <HDRPIXEL> floatBuffer = loadPfm(newPath.c_str(), &std::cerr);

                        if (!floatBuffer.data.empty())
                        {
                            tex.target = GL_TEXTURE_2D;

                            // attempt to re-use texture handle
                            if (tex.handle == 0)
                            {
                                glGenTextures(1, &tex.handle);
                                ERROR_CHECK;
                            }

                            // pick texture formats
                            int original_texture_format = GL_RGB;
                            int internal_texture_format = GL_RGB32F;

                            // bind the texture id
                            glBindTexture(tex.target, tex.handle);
                            ERROR_CHECK;

                            // upload the texture
                            const int level = 0;
                            const int border = 0;
                            glTexImage2D(
                                tex.target,
                                level,
                                internal_texture_format,
                                floatBuffer.getColumns(),
                                floatBuffer.getRows(),
                                border,
                                original_texture_format,
                                GL_FLOAT,
                                &floatBuffer.data[0]);
                            ERROR_CHECK;
                        }
                    }
                    else
                    {
                        tex.handle = SOIL_load_OGL_texture(newPath.c_str(), SOIL_LOAD_AUTO, tex.handle, 
                                                           SOIL_FLAG_MIPMAPS | 
                                                           SOIL_FLAG_DDS_LOAD_DIRECT | 
                                                           (srgb ? SOIL_FLAG_SRGB : 0) |
                                                           0);
                                                           ERROR_CHECK;
                        tex.target = GL_TEXTURE_2D;
                    }
                }

				if (!tex.handle)
				{
					logError("Couldn't load texture node "+node->getName()+" from file "+newPath);
				}
				else
					logOutput(std::string("Loaded texture ")+newPath);
			}
			tex.path = newPath;
			
			modelsOrTexturesTouched = true;
		}
	}
	else if (node->getType() == NodeTypes::getType(NodeTypes::RENDER_TARGET) && !doFastUpdate) // render target
	{
		float w(512),h(512);
		node->getField("width", w);
		node->getField("height", h);
		
		bool multiple(false);
		node->getField("width/height are multiples of framebuffer", multiple);
		
		int wi((int)w), hi((int)h);
		if (multiple)
		{
			wi = (int)w*width;
			hi = (int)h*height;
		}
		
		// update any time any fields change, or if the width/height has changed
		if (node->getFields() != tex.fieldCache || tex.width != wi || tex.height != hi)
		{
			tex.fieldCache = node->getFields();
			
			deleteTexture(tex);
			
			tex.origw = w;
			tex.origh = h;
			tex.origMultiples = multiple;
			
			bool mipmap(false);
			node->getField("auto-generate mipmap", mipmap);
			tex.autoMipmap = mipmap;
			
			std::string formatstr;
			node->getField("format", formatstr);
			if (formatstr.empty())
			{
				logError("Render target node "+node->getName()+" has no format selected");
			}
			else
			{
				GLenum internalFormat = GLEnumHelper.getEnum(formatstr);
				tex.internalFormat = internalFormat;
				GLenum format = GL_NONE;
				if (formatstr.find("GL_RGBA") == 0)
					format = GL_RGBA;
				if (formatstr.find("GL_SRGB8_ALPHA8") == 0)
					format = GL_RGBA;
				else if (formatstr.find("GL_RG") == 0)
					format = GL_RG;
				else if (formatstr.find("GL_R") == 0)
					format = GL_RED;
				else if (formatstr.find("GL_DEPTH_COMPONENT") == 0)
					format = GL_DEPTH_COMPONENT;
				
				if (format == GL_NONE)
				{
					 std::cerr << "Unhandled internalFormat: " << formatstr << std::endl;
					 assert(!"Unhandled internalFormat");
				}
				
				// default types
				GLenum type = GL_UNSIGNED_BYTE;
				if (formatstr.find("16") != std::string::npos)
					type = GL_UNSIGNED_SHORT;
				if (formatstr.find("32") != std::string::npos)
					type = GL_UNSIGNED_INT;
				
				if (formatstr.find("32F") != std::string::npos)
					type = GL_FLOAT;
				else if (formatstr.find("16F") != std::string::npos)
					type = GL_HALF_FLOAT;
				else if (formatstr.find("DEPTH_COMPONENT") != std::string::npos)
					type = GL_UNSIGNED_INT;
				else if (formatstr.find("8UI") != std::string::npos)
				{
					type = GL_UNSIGNED_BYTE;
					format = toIntegerFormat(format);
				}
				else if (formatstr.find("16UI") != std::string::npos)
				{
					type = GL_UNSIGNED_SHORT;
					format = toIntegerFormat(format);
				}
				else if (formatstr.find("32UI") != std::string::npos)
				{
					type = GL_UNSIGNED_INT;
					format = toIntegerFormat(format);
				}
				else if (formatstr.find("8I") != std::string::npos)
				{
					type = GL_BYTE;
					format = toIntegerFormat(format);
				}
				else if (formatstr.find("16I") != std::string::npos)
				{
					type = GL_SHORT;
					format = toIntegerFormat(format);
				}
				else if (formatstr.find("32I") != std::string::npos)
				{
					type = GL_INT;
					format = toIntegerFormat(format);
				}
				
				tex.target = GL_TEXTURE_2D;
				
				// go ahead and create the texture!
				glGenTextures(1, &tex.handle); ERROR_CHECK;
				glBindTexture(tex.target, tex.handle); ERROR_CHECK;
				glTexImage2D(tex.target, 0, internalFormat, wi, hi, 0, format, type, NULL); ERROR_CHECK;
				
				// set some default texture parameters for now.
				// when we actually sample the texture we should set parameters that
				// match what we want in our texture, however...
				// this is only here to work around a problem with my geforce 7
				// drivers, where they will say the framebuffer setup is unsupported
				// unless they know some texture parameters from when we originally
				// create the texture.
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
				
				glBindTexture(tex.target, 0); ERROR_CHECK; // don't leave it bound
				tex.width = wi;
				tex.height = hi;
				
				logOutput("Generated render target \""+node->getName()+"\": size "+tostr(tex.width)+"x"+tostr(tex.height)+
					" target "+GLEnumHelper.getEnum(tex.target)+
					" internalFormat "+GLEnumHelper.getEnum(internalFormat)+
					" format "+GLEnumHelper.getEnum(format)+
					" type "+GLEnumHelper.getEnum(type)+
					" handle "+tostr(tex.handle));
			}
		}
	}
}

void Renderer::addTexture(std::pair<RenderTexture &, const SceneNode *> args)
{
	updateTexture(args);
}

void Renderer::deleteShader(RenderShader & shader)
{
	if (shader.handle != 0)
	{
		glDeleteShader(shader.handle);ERROR_CHECK;
		shader.handle = 0;
	}
}

void Renderer::updateShader(std::pair<RenderShader &, const SceneNode *> args)
{
	RenderShader & shader = args.first;
	const SceneNode * node = args.second;
	std::string newPath;
	node->getField("path", newPath);
	
	if (newPath != shader.path || shader.modTime != FileModificationTime(newPath))
	{
		deleteShader(shader);
		addShader(args);ERROR_CHECK;
		modelsOrTexturesTouched = true; // this is necessary to get the shader sampler variables rebound
	}
}

bool loadFileIntoString(const std::string & filepath, std::string & output)
{
	std::ifstream f;
	f.open(filepath.c_str());
	if (f)
	{
		char c[1024];
		
		while (f.good())
		{
			f.get(c, 1024, 0);
			output = output + c;
		}
		
		f.close();
		
		return true;
	}
	else
	{
		return false;
	}
}

void Renderer::addShader(std::pair<RenderShader &, const SceneNode *> args)
{
	RenderShader & shader = args.first;
	const SceneNode * node = args.second;
	node->getField("path", shader.path);
	
	shader.modTime = FileModificationTime(shader.path);
	
	shader.handle = 0;
	
	std::string shaderSource;
	if (!loadFileIntoString(shader.path, shaderSource))
	{
		if (!shader.path.empty())
			logError("Couldn't load shader node "+node->getName()+" from file "+shader.path);
	}
	else
	{
		// process defines into #define text to insert at the top of the shader
		std::string definestring;
		node->getField("defines", definestring);
		// tokenize defines
		std::vector <std::string> defines = explode(definestring, " ");
		// create a define block
		std::stringstream blockstream;
		shader.defines.clear();
		for (int i = 0; i < defines.size(); i++)
		{
			if (!defines[i].empty())
			{
				blockstream << "#define " << defines[i] << std::endl;
				shader.defines.insert(defines[i]);
			}
		}
		// insert the define block at the top of the shader, but after the "#version" line, if it exists
		if (shaderSource.substr(0,8) == "#version")
		{
			if (shaderSource.find('\n') != std::string::npos && shaderSource.find('\n') + 1 < shaderSource.size())
				shaderSource.insert(shaderSource.find('\n')+1, blockstream.str());
		}
		else
		{
			shaderSource = blockstream.str() + shaderSource;
		}
		
		GLenum shaderType = GL_VERTEX_SHADER;
		if (node->getType() == "fragment shader")
			shaderType = GL_FRAGMENT_SHADER;
		
		shader.handle = glCreateShader(shaderType);ERROR_CHECK;
		const GLchar * shaderSourcePointer = shaderSource.c_str();
		glShaderSource(shader.handle, 1, &shaderSourcePointer, NULL);ERROR_CHECK;
		glCompileShader(shader.handle);ERROR_CHECK;
		GLint compileStatus(0);
		glGetShaderiv(shader.handle, GL_COMPILE_STATUS, &compileStatus);
		if (!compileStatus)
		{
			GLint bufferSize(0);
			glGetShaderiv(shader.handle, GL_INFO_LOG_LENGTH, &bufferSize);
			GLchar infoLog[bufferSize+1];
			GLsizei infoLogLength;
			glGetShaderInfoLog(shader.handle, bufferSize, &infoLogLength, infoLog);
			infoLog[bufferSize] = '\0';
			logError("Compilation of shader node "+node->getName()+" from file "+shader.path+" failed\n"+infoLog);
			glDeleteShader(shader.handle);ERROR_CHECK;
			shader.handle = 0;
		}
		else
		{
			logOutput("Successfully compiled shader "+shader.path);
		}
		
		ERROR_CHECK;
	}
	
	for (keyed_container <RenderPass>::iterator i = passes.begin(); i != passes.end(); i++)
	{
		SceneMapping::iterator s = shaderMap.find(node);
		assert(s != shaderMap.end());
		handleShaderUpdate(*i, s->second, shader);
	}
}

void Renderer::deleteModel(RenderModel & model)
{
	glBindBuffer(GL_ARRAY_BUFFER,0);ERROR_CHECK;
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);ERROR_CHECK;
	if (!model.vbos.empty())
	{
		glDeleteBuffers(model.vbos.size(), &model.vbos[0]);ERROR_CHECK;
	}
	if (model.elementVbo != 0)
	{
		glDeleteBuffers(1, &model.elementVbo);ERROR_CHECK;
		model.elementVbo = 0;
	}
	if (model.vao != 0)
	{
		glBindVertexArray(0);
		glDeleteVertexArrays(1,&model.vao);ERROR_CHECK;
		logOutput(std::string("Unloaded model ")+model.path);
		model.vao = 0;
	}
	ERROR_CHECK;
}

void Renderer::updateModel(std::pair<RenderModel &, const SceneNode *> args)
{
	RenderModel & model = args.first;
	const SceneNode * node = args.second;
	std::string newPath;
	node->getField("path", newPath);
	
	if (!newPath.empty() && 
		(model.fieldCache != node->getFields() ||
		model.modTime != FileModificationTime(newPath)))
	{
		deleteModel(model);
		addModel(args);ERROR_CHECK;
		modelsOrTexturesTouched = true;
	}
}

void Renderer::addModel(std::pair<RenderModel &, const SceneNode *> args)
{
	RenderModel & model = args.first;
	const SceneNode * node = args.second;
	node->getField("path", model.path);
	
	model.fieldCache = node->getFields();
	model.modTime = FileModificationTime(model.path);
	
	// attempt the load
	const struct aiScene * scene = aiImportFile(model.path.c_str(), aiProcessPreset_TargetRealtime_Quality);
	if (!scene || scene->mNumMeshes < 1)
	{
		model.vao = 0;
		if (!model.path.empty())
			logError("Couldn't load model node "+node->getName()+" from file "+model.path+": "+aiGetErrorString());
	}
	else
	{
		const struct aiMesh * mesh = scene->mMeshes[0];
		
		// build faces; we'll use these below to generate a face index vbo
		std::vector <GLuint> faces(mesh->mNumFaces*3);
		for (int t = 0; t < mesh->mNumFaces; t++)
		{
			assert(t*3+2 < faces.size());
			
			const struct aiFace * face = &mesh->mFaces[t];
			if (face->mNumIndices != 3)
			{
				logError("Couldn't load model node "+node->getName()+" from file "+model.path+": non-triangle objects are present");
				return;
			}
			for (int i = 0; i < 3; i++)
			{
				int index = face->mIndices[i];
				faces[t*3+i] = index;
			}
		}
		
		// generate vertex array
		glGenVertexArrays(1, &model.vao);ERROR_CHECK;
		glBindVertexArray(model.vao);ERROR_CHECK;
		
		// generate vertex buffer for face indices
		{
			glGenBuffers(1, &model.elementVbo);ERROR_CHECK;
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, model.elementVbo);ERROR_CHECK;
			glBufferData(GL_ELEMENT_ARRAY_BUFFER, faces.size()*sizeof(GLuint), &faces[0], GL_STATIC_DRAW);ERROR_CHECK;
			model.elementCount = faces.size();
		}
		
		// construct vertex buffers for generic vertex attribs using this scheme:
		// 0: position
		// 1: normal
		// 2: tangent
		// 3: bitangent
		// 4: color
		// 5: uv0
		// 6: uv1
		// 7: uv2
		//const int elements = 4+mesh->GetNumColorChannels()+std::min(3U,mesh->GetNumUVChannels());
		const int elements = 8;
		for (int i = 0; i < elements; i++)
		{
			const GLvoid * data(NULL);
			int size = 3;
			switch (i)
			{
				case 0:
				data = mesh->mVertices;
				break;
				
				case 1:
				data = mesh->mNormals;
				break;
				
				case 2:
				data = mesh->mTangents;
				break;
				
				case 3:
				data = mesh->mBitangents;
				break;
				
				case 4:
				if (mesh->GetNumColorChannels() > 0)
					data = mesh->mColors[0];
				size = 4;
				break;
				
				case 5:
				case 6:
				case 7:
				int uvIndex = i - 5;
				if (mesh->GetNumUVChannels() > uvIndex)
				{
					data = mesh->mTextureCoords[uvIndex];
				}
				break;
			}
			if (data)
			{
				GLuint vboHandle;
				glGenBuffers(1, &vboHandle);ERROR_CHECK;
				model.vbos.push_back(vboHandle);
				glBindBuffer(GL_ARRAY_BUFFER, vboHandle);ERROR_CHECK;
				glBufferData(GL_ARRAY_BUFFER, mesh->mNumVertices*size*sizeof(float), data, GL_STATIC_DRAW);ERROR_CHECK;
				glVertexAttribPointer(i, size, GL_FLOAT, GL_FALSE, 0, 0);ERROR_CHECK;
				glEnableVertexAttribArray(i);ERROR_CHECK;
			}
			else
			{
				//std::cout << "Mesh is missing attribute: " << i << std::endl;
				
				switch (size)
				{
					case 3:
					glVertexAttrib3f(i, 0.0, 0.0, 0.0);
					break;
					
					case 4:
					glVertexAttrib4f(i, 0.0, 0.0, 0.0, 1.0);
					break;
					
					default:
					assert(0);
					break;
				}
				ERROR_CHECK;
			}
		}
		
		aiReleaseImport(scene);
		
		logOutput(std::string("Loaded model ")+model.path);
		
		ERROR_CHECK;
	}
}

void Renderer::rebindModelVaos()
{
	// if any vertex attribute array is disabled, the VAO loses its binding to corresponding VBO.
	// we have to call again glBindBuffer() and glVertexAttribPointer() functions.
	// the specification tells nothing about this feature, but it is what we have to do with current version of NVidia drivers.
	
	for (keyed_container <RenderModel>::iterator i = models.begin(); i != models.end(); i++)
	{
		if (i->vao != 0)
		{
			glBindVertexArray(i->vao);ERROR_CHECK;
			
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i->elementVbo);ERROR_CHECK;
			
			for (std::vector <GLuint>::iterator v = i->vbos.begin(); v != i->vbos.end(); v++)
			{
				glBindBuffer(GL_ARRAY_BUFFER, *v);ERROR_CHECK;
			}
		}
	}
}

template <typename T>
void handleUpdatedNodes(Renderer & renderer,
						keyed_container <RenderPass> * passes,
						const std::vector <const SceneNode *> & nodes,
						Renderer::SceneMapping & mapping,
						keyed_container <T> & container,
						std::tr1::function <void (T&)> deleteFunction,
						std::tr1::function <void (std::pair<T &, const SceneNode *>)> updateFunction,
						std::tr1::function <void (std::pair<T &, const SceneNode *>)> addFunction)
{
	NodeSet added, deleted;
	getAddedDeleted(nodes, mapping, added, deleted);
	
	// delete
	for (NodeSet::const_iterator i = deleted.begin(); i != deleted.end(); i++)
	{
		keyed_container_handle handle = mapping[*i];
		assert(handle.valid());
		T & value = container.get(handle);
		
		// notify passes about deleted nodes
		if (passes)
		{
			for (keyed_container <RenderPass>::iterator p = passes->begin(); p != passes->end(); p++)
			{
				renderer.handleDelete(*p, handle, value);
			}
		}
		
		deleteFunction(value);
		
		mapping.erase(*i);
		container.erase(handle);
	}
	
	// check for updates
	for (Renderer::SceneMapping::iterator i = mapping.begin(); i != mapping.end(); i++)
	{
		T & value = container.get(i->second);
		std::pair<T &, const SceneNode *> args(value, i->first);
		updateFunction(args);
	}
	
	// add
	for (NodeSet::const_iterator i = added.begin(); i != added.end(); i++)
	{
		T value;
		keyed_container_handle handle = container.insert(value);
		mapping[*i] = handle;
		
		std::pair<T &, const SceneNode *> args(container.get(handle), *i);
		addFunction(args);
	}
}

void Renderer::deletePass(RenderPass & pass)
{
	// delete shader program
	if (pass.shaderProgram != 0)
	{
		glDeleteProgram(pass.shaderProgram);ERROR_CHECK;
		pass.shaderProgram = 0;
	}
}

void Renderer::handleDelete(RenderPass & pass, keyed_container_handle handle, RenderShader & shader)
{
	if (pass.vertexShader == handle)
		pass.vertexShader.invalidate();
	if (pass.fragmentShader == handle)
		pass.fragmentShader.invalidate();
}

void Renderer::handleShaderUpdate(RenderPass & pass, keyed_container_handle handle, RenderShader & shader)
{
	if (pass.vertexShader == handle || pass.fragmentShader == handle)
		passLinkShaderProgramInternal(pass, "");
}

void Renderer::updatePass(std::pair<RenderPass &, const SceneNode *> args)
{
	RenderPass & pass = args.first;
	const SceneNode * node = args.second;
	
	// this function is smart enough to do nothing if nothing has changed
	// also, it will automatically delete the existing pass if necessary
	passLinkShaderProgram(pass, node, false);
	
	// set up models, textures, and variables
	if (modelsOrTexturesTouched || !doFastUpdate)
		passAssignModels(pass, node);
	
	if (!doFastUpdate)
	{
		// setup render state
		passSetState(pass, node);
		
		// setup framebuffer outputs
		passSetFBO(pass, node);
		
		// configuration stored in the pass node
		passSetConfiguration(pass, node);
	}
}

void Renderer::passSetConfiguration(RenderPass & pass, const SceneNode * node)
{
	bool clearDepth = true;
	node->getField("clear depth", clearDepth);
	
	bool clearColor = true;
	node->getField("clear color", clearColor);
	
	bool clearStencil = true;
	node->getField("clear stencil", clearStencil);
	
	std::string clearColorStr;
	node->getField("clear color value", clearColorStr);
	arrayfromstr(clearColorStr, pass.clearColor, 4);
	
	node->getField("clear depth value", pass.clearDepth);
	node->getField("clear stencil value", pass.clearStencil);
	
	pass.clearMask = 0;
	if (clearDepth)
		pass.clearMask = pass.clearMask | GL_DEPTH_BUFFER_BIT;
	if (clearColor)
		pass.clearMask = pass.clearMask | GL_COLOR_BUFFER_BIT;
	if (clearStencil)
		pass.clearMask = pass.clearMask | GL_STENCIL_BUFFER_BIT;
	
	node->getField("enable", pass.enable);
}

void Renderer::assignUniformData(RenderPass::UniformData & uniform, SceneNode * variableNode)
{
	if (variableNode->getType() == NodeTypes::getType(NodeTypes::PERSPECTIVE_CAMERA) ||
		variableNode->getType() == NodeTypes::getType(NodeTypes::PERSPECTIVE_ASPECT_CAMERA))
	{
		// special case for cameras: they have no float vars, we need to build our own
		uniform.data = std::tr1::shared_ptr <std::vector <float> >(new std::vector <float>());
		MATRIX4 <double> matrix;
		float fovy(45), aspect(1), zNear(1), zFar(1000);
		variableNode->getField("vertical field of view", fovy);
		
		// auto-fill aspect for non-aspect cameras
		if (variableNode->getType() == NodeTypes::getType(NodeTypes::PERSPECTIVE_CAMERA))
		{
			aspect = (float)width/height;
		}
		else if (variableNode->getType() == NodeTypes::getType(NodeTypes::PERSPECTIVE_ASPECT_CAMERA))
		{
			variableNode->getField("aspect", aspect);
		}
		
		variableNode->getField("z near", zNear);
		variableNode->getField("z far", zFar);
		
		bool inverse(false);
		variableNode->getField("inverse", inverse);
		if (inverse)
		{
			matrix = MATRIX4<double>::InvPerspective(fovy, aspect, zNear, zFar);
		}
		else
		{
			matrix.SetPerspective(fovy, aspect, zNear, zFar);
		}
		for (int i = 0; i < 16; i++)
			uniform.data->push_back(matrix[i]);
	}
	else if (variableNode->getType() == NodeTypes::getType(NodeTypes::ORTHOGRAPHIC_CAMERA))
	{
		// special case for cameras: they have no float vars, we need to build our own
		uniform.data = std::tr1::shared_ptr <std::vector <float> >(new std::vector <float>());
		MATRIX4 <double> matrix;
		float left(-1), right(1), bottom(-1), top(1), zNear(1), zFar(1000);
		variableNode->getField("left", left);
		variableNode->getField("right", right);
		variableNode->getField("bottom", bottom);
		variableNode->getField("top", top);
		variableNode->getField("z near", zNear);
		variableNode->getField("z far", zFar);
		matrix.SetOrthographic(left, right, bottom, top, zNear, zFar);
		for (int i = 0; i < 16; i++)
			uniform.data->push_back(matrix[i]);
	}
	else if (variableNode->getType() == NodeTypes::getType(NodeTypes::VIEWPORT_SIZE))
	{
		// special case for viewport size variable: it has no float vars, we need to build our own
		uniform.data = std::tr1::shared_ptr <std::vector <float> >(new std::vector <float>());
		uniform.data->push_back(width);
		uniform.data->push_back(height);
	}
	else if (variableNode->getType() == NodeTypes::getType(NodeTypes::TRANSFORMATION))
	{
		// special case for transformations: allow inversion
		bool invert;
		variableNode->getField("invert", invert);
		if (invert)
		{
			uniform.data = std::tr1::shared_ptr <std::vector <float> >(new std::vector <float>());
			MATRIX4 <double> matrix;
			matrix.Set(&(*variableNode->getFloatVars())[0]);
			matrix = matrix.Inverse();
            uniform.data->clear();
			for (int i = 0; i < 16; i++)
				uniform.data->push_back(matrix[i]);
		}
		else
			uniform.data = variableNode->getFloatVars();
	}
	else
	{
		uniform.data = variableNode->getFloatVars();
	}
}

/*class StateHelper
{
	public:
		StateHelper()
		{
			RenderStateTypes["TEXTURE_BASE_LEVEL"] = RenderPass::RenderState::SS_INT;
			RenderStateTypes["TEXTURE_BORDER_COLOR"] = RenderPass::RenderState::SS_FLOAT4;
			RenderStateTypes["TEXTURE_COMPARE_MODE"] = RenderPass::RenderState::SS_ENUM;
			RenderStateTypes["TEXTURE_COMPARE_FUNC"] = RenderPass::RenderState::SS_ENUM;
			RenderStateTypes["TEXTURE_LOD_BIAS"] = RenderPass::RenderState::SS_FLOAT;
			RenderStateTypes["TEXTURE_MAG_FILTER"] = RenderPass::RenderState::SS_ENUM;
			RenderStateTypes["TEXTURE_MAX_LEVEL"] = RenderPass::RenderState::SS_INT;
			RenderStateTypes["TEXTURE_MAX_LOD"] = RenderPass::RenderState::SS_FLOAT;
			RenderStateTypes["TEXTURE_MIN_FILTER"] = RenderPass::RenderState::SS_ENUM;
			RenderStateTypes["TEXTURE_MIN_LOD"] = RenderPass::RenderState::SS_FLOAT;
			RenderStateTypes["TEXTURE_SWIZZLE_R"] = RenderPass::RenderState::SS_ENUM;
			RenderStateTypes["TEXTURE_SWIZZLE_G"] = RenderPass::RenderState::SS_ENUM;
			RenderStateTypes["TEXTURE_SWIZZLE_B"] = RenderPass::RenderState::SS_ENUM;
			RenderStateTypes["TEXTURE_SWIZZLE_A"] = RenderPass::RenderState::SS_ENUM;
			RenderStateTypes["TEXTURE_SWIZZLE_RGBA"] = RenderPass::RenderState::SS_ENUM4;
			RenderStateTypes["TEXTURE_WRAP_S"] = RenderPass::RenderState::SS_ENUM;
			RenderStateTypes["TEXTURE_WRAP_T"] = RenderPass::RenderState::SS_ENUM;
			RenderStateTypes["TEXTURE_WRAP_R"] = RenderPass::RenderState::SS_ENUM;
		}
		
		bool getRenderStateType(const std::string & name, RenderPass::RenderState::RenderStateType & output) const
		{
			// strip off the GL_ prefix
			std::map <std::string, RenderPass::RenderState::RenderStateType>::const_iterator i = RenderStateTypes.find(name.substr(3));
			if (i == RenderStateTypes.end())
				return false;
			else
			{
				output = i->second;
				return true;
			}
		}
		
	private:
		std::map <std::string, RenderPass::RenderState::RenderStateType> RenderStateTypes;
		
} stateHelper;*/

bool getStateType(const std::string & strtype, RenderPass::RenderState::RenderStateType & output)
{
	if (strtype.substr(0,6) == "select")
	{
		output = RenderPass::RenderState::SS_ENUM;
		return true;
	}
	else if (strtype == "float")
	{
		output = RenderPass::RenderState::SS_FLOAT;
		return true;
	}
	else if (strtype == "float2")
	{
		output = RenderPass::RenderState::SS_FLOAT2;
		return true;
	}
	else if (strtype == "float4" || strtype == "color")
	{
		output = RenderPass::RenderState::SS_FLOAT4;
		return true;
	}
	else if (strtype == "int")
	{
		output = RenderPass::RenderState::SS_INT;
		return true;
	}
	else
	{
		return false;
	}
}

RenderPass::RenderState::RenderState(const std::string & name, RenderStateType newtype, const std::string & value)
{
	pname = GLEnumHelper.getEnum(name);
	type = newtype;
	switch (type)
	{
		case RenderPass::RenderState::SS_ENUM:
		param[0] = GLEnumHelper.getEnum(value);
		break;

		/*case RenderPass::RenderState::SS_ENUM4:
		{
			std::vector <std::string> enumvec = explode(value, ",");
			for (int i = 0; i < enumvec.size() && i < 4; i++)
			{
				param[i] = GLEnumHelper.getEnum(enumvec[i]);
			}
		}
		break;*/

		case RenderPass::RenderState::SS_INT:
		param[0] = fromstr<int>(value);
		break;

		case RenderPass::RenderState::SS_FLOAT:
		fparam[0] = fromstr<float>(value);
		break;
		
		case RenderPass::RenderState::SS_FLOAT2:
		arrayfromstr(value, fparam, 2);
		break;

		case RenderPass::RenderState::SS_FLOAT4:
		arrayfromstr(value, fparam, 4);
		break;

		default:
		assert(!"Invalid sampler state type");
	}
}

void Renderer::passAssignModels(RenderPass & pass, const SceneNode * node)
{
	pass.models.clear();
	pass.defaultUniforms.clear();
	pass.defaultTextureBindings.clear();
	pass.samplers.clear();
	pass.variableNameToUniformLocation.clear();
	pass.textureNameToTextureUnit.clear();
    pass.variableNameToDefaultValue.clear();
	
	// When we add a model, we need to assign any uniform overrides at the same time.
	// We need to build the default uniform values, and also keep information around
	// that we can use later to populate the overrides.
	std::tr1::unordered_map <std::string, RenderPass::UniformData> uniforms;
	{
		std::vector <const SceneNode *> nodes;
		node->getChildren(nodes, true, "category:variable", NULL, NodeTypes::getType(NodeTypes::DRAW_GROUP)); // all non-draw-groups under this pass
		sceneRoot->getChildren(nodes, false, "category:variable", node, NodeTypes::getType(NodeTypes::DRAW_GROUP)); // all non-draw-groups under root and possibly under this pass
		for (std::vector <const SceneNode *>::const_iterator i = nodes.begin(); i != nodes.end(); i++)
		{
			// add the uniform to the map
			// ignore duplicates
			std::string uniformName = (*i)->getName();
			if (uniforms.find(uniformName) == uniforms.end() && pass.shaderProgram != 0)
			{
				RenderPass::UniformData uniform;
				uniform.location = glGetUniformLocation(pass.shaderProgram, uniformName.c_str()); ERROR_CHECK;
				if (uniform.location == -1)
				{
					std::string vertShaderName = shaders.get(pass.vertexShader).path;
					std::string fragShaderName = shaders.get(pass.fragmentShader).path;
					//logError("Variable "+uniformName+" doesn't have a corresponding uniform in shaders "+vertShaderName+" or "+fragShaderName);
				}
				else
				{
					SceneNode * variableNode = const_cast<SceneNode*>(*i);
					assignUniformData(uniform, variableNode);
					assert(uniform.data);
					uniforms.insert(std::make_pair(uniformName, uniform));
					pass.variableNameToUniformLocation.insert(std::make_pair(uniformName, uniform.location));
				}

                // store export-only info
                {
                    RenderPass::UniformData defaultValues;
                    assignUniformData(defaultValues, const_cast<SceneNode*>(*i));
                    assert(defaultValues.data);
                    pass.variableNameToDefaultValue.insert(std::make_pair(uniformName, defaultValues));
                }
			}
		}
	}
	
	// When we add a model, we need to assign texture units at the same time.
	// To be able to do that, we need to collect information about samplers
	// in the scene.
	std::tr1::unordered_map <std::string, RenderPass::TextureData> textureBindings; // default texture bindings
	std::vector <std::string> textureUnitToTextureName; // indexed by texture unit
	{
		std::vector <const SceneNode *> nodes;
		node->getChildren(nodes, false, NodeTypes::getType(NodeTypes::SAMPLER));
		std::vector <const SceneNode *> textureNodes;
		node->getChildren(textureNodes, true, "category:texture", NULL, NodeTypes::getType(NodeTypes::DRAW_GROUP)); // all non-draw-groups under this pass
		sceneRoot->getChildren(textureNodes, true, "category:texture", node, NodeTypes::getType(NodeTypes::DRAW_GROUP)); // all non-draw-groups under root and possibly under this pass

        // for each sampler under this pass
		for (std::vector <const SceneNode *>::const_iterator i = nodes.begin(); i != nodes.end(); i++)
		{
			// which texture unit this sampler will be assigned to
			const GLint curTU = pass.samplers.size();
			
			// setup the binding between texture unit and sampler variable
			std::string samplerName = (*i)->getName();
			if (pass.shaderProgram != 0 && !samplerName.empty())
			{
				// setup binding between TU and shader sampler uniform
				GLint samplerLocation = glGetUniformLocation(pass.shaderProgram, samplerName.c_str());ERROR_CHECK;
				if (samplerLocation == -1)
				{
					std::string vertShaderName = shaders.get(pass.vertexShader).path;
					std::string fragShaderName = shaders.get(pass.fragmentShader).path;
					//logError("Sampler "+samplerName+" doesn't have a corresponding uniform in shaders "+vertShaderName+" or "+fragShaderName);
				}
				else
				{
					glUseProgram(pass.shaderProgram);ERROR_CHECK;
					glUniform1i(samplerLocation, curTU);ERROR_CHECK;
				}
			}
			
			// setup sampler state
			pass.samplers.push_back(RenderPass::Sampler());
			RenderPass::Sampler & RenderStateVector = pass.samplers.back();
			RenderStateVector.name = samplerName;
			const std::map <std::string, SceneNodeField> & fields = (*i)->getFields();
			for (std::map <std::string, SceneNodeField>::const_iterator f = fields.begin(); f != fields.end(); f++)
			{
				// automatically generate RenderState structs based on the fields,
				// but skip the non-state fields or blank fields
				if (f->first != "texture" && !f->second.getValue().empty())
				{
					// see if this field name matches a sampler state name
					RenderPass::RenderState::RenderStateType sstype;
					//if (stateHelper.getRenderStateType(f->first, sstype))
					if (getStateType(f->second.getType(), sstype))
					{
						// handle converting the field's string value into a RenderPass::RenderState
						RenderPass::RenderState state(f->first, sstype, f->second.getValue());
						RenderStateVector.state.push_back(state);
					}
					else
					{
						logError("Unknown sampler state type: "+f->first);
					}
				}
			}
			
			// find the texture name for this TU
			std::string textureName;
			(*i)->getField("texture", textureName);
			textureUnitToTextureName.push_back(textureName);
			
			// search through textures to find a default node
			const SceneNode * defaultNode(NULL);
			for (std::vector <const SceneNode *>::const_iterator t = textureNodes.begin(); t != textureNodes.end(); t++)
			{
				if ((*t)->getName() == textureName)
				{
					defaultNode = *t;
					break;
				}
			}
			
			// create a default texture binding if we found a default node
			if (defaultNode)
			{
				SceneMapping::iterator textureHandleIter = textureMap.find(defaultNode);
				if (textureHandleIter != textureMap.end())
				{
					RenderPass::TextureData defaultTextureBind;
					defaultTextureBind.tu = curTU;
					defaultTextureBind.texture = textureHandleIter->second;
					textureBindings.insert(std::make_pair(textureName, defaultTextureBind));
				}
			}
			
			// store the texture node name
			pass.textureNameToTextureUnit.insert(std::make_pair(textureName, curTU));
		}
	}
	
	// get a list of scenes inside this pass
	std::vector <const SceneNode *> nodes;
	node->getChildren(nodes, false, NodeTypes::getType(NodeTypes::SCENE));
	for (std::vector <const SceneNode *>::const_iterator i = nodes.begin(); i != nodes.end(); i++)
	{
		std::string targetGroup;
		(*i)->getField("draw group", targetGroup);
		
		if (!targetGroup.empty())
		{
			// get a list of reference-able group nodes
			std::vector <const SceneNode *> groupNodes;
			sceneRoot->getChildren(groupNodes, true, NodeTypes::getType(NodeTypes::DRAW_GROUP), node);
			
			// search the group nodes for the node the scene refers to
			for (std::vector <const SceneNode *>::const_iterator g = groupNodes.begin(); g != groupNodes.end(); g++)
			{
				if ((*g)->getName() == targetGroup)
				{
					// get a list of models inside the group node
					std::vector <const SceneNode *> modelNodes;
					(*g)->getChildren(modelNodes, true, NodeTypes::getType(NodeTypes::MODEL));
					
					// add models to the pass
					for (std::vector <const SceneNode *>::const_iterator m = modelNodes.begin(); m != modelNodes.end(); m++)
					{
						RenderPass::ModelContext context;
						SceneMapping::iterator modelIterator = modelMap.find(*m);
						assert(modelIterator != modelMap.end());
						context.model = modelIterator->second;
						
						// setup the textures for each TU
						int curTU = 0;
						for (std::vector <std::string>::const_iterator tu = textureUnitToTextureName.begin(); tu != textureUnitToTextureName.end(); tu++,curTU++)
						{
							keyed_container<RenderTexture>::handle textureHandle;
							
							// search for an override
							std::vector <const SceneNode *> customNodes;
							(*m)->getChildren(customNodes, false, NodeTypes::getType(NodeTypes::TEXTURE));
							for (std::vector <const SceneNode *>::const_iterator c = customNodes.begin(); c != customNodes.end(); c++) // for each override
							{
								if ((*c)->getName() == *tu) // the override matches the texture name for this TU
								{
									SceneMapping::iterator textureHandleIter = textureMap.find(*c);
									if (textureHandleIter != textureMap.end())
									{
										textureHandle = textureHandleIter->second;
									}
								}
							}
							
							// if we found an override, add it to the model context
							if (textureHandle.valid())
							{
								RenderPass::TextureData textureBindOverride;
								textureBindOverride.tu = curTU;
								textureBindOverride.texture = textureHandle;
								context.textureBindingOverrides.push_back(textureBindOverride);
							}
						}
						
						// setup any uniform overrides
						{
							std::vector <const SceneNode *> customNodes;
							(*m)->getChildren(customNodes, false, "category:variable");
							for (std::vector <const SceneNode *>::const_iterator c = customNodes.begin(); c != customNodes.end(); c++)
							{
								// for each variable node that we found, see if a default value exists
								std::string uniformName = (*c)->getName();
								std::tr1::unordered_map <std::string, RenderPass::UniformData>::const_iterator defaultValue = uniforms.find(uniformName);
								if (defaultValue == uniforms.end())
								{
									//logError("Model "+(*m)->getName()+" has an override for variable "+(*c)->getName()+" but there's no base variable node.");
									
									// if we encounter a variable node override with no default value, create a default value node
									RenderPass::UniformData uniform;
									uniform.location = glGetUniformLocation(pass.shaderProgram, uniformName.c_str()); ERROR_CHECK;
									if (uniform.location == -1)
									{
										std::string vertShaderName = shaders.get(pass.vertexShader).path;
										std::string fragShaderName = shaders.get(pass.fragmentShader).path;
										logError("Variable "+uniformName+" doesn't have a corresponding uniform in shaders "+vertShaderName+" or "+fragShaderName);
									}
									else
									{
										SceneNode * variableNode = const_cast<SceneNode*>(*c);
										assignUniformData(uniform, variableNode);
										assert(uniform.data);
										uniforms.insert(std::make_pair(uniformName, uniform));
										pass.variableNameToUniformLocation.insert(std::make_pair(uniformName, uniform.location));
										
										if (uniform.data->empty())
											logError("Empty override variable: "+uniformName+" location "+tostr(uniform.location)+" size "+tostr(uniform.data->size())+" node "+variableNode->getName());
										
										// don't need to bother populating an override, because we now match the default value ;-)
									}

                                    // store export-only info
                                    {
                                        RenderPass::UniformData defaultValues;
                                        assignUniformData(defaultValues, const_cast<SceneNode*>(*c));
										assert(defaultValues.data);
                                        pass.variableNameToDefaultValue.insert(std::make_pair(uniformName, defaultValues));
                                    }
								}
								else
								{
									RenderPass::UniformData override;
									SceneNode * variableNode = const_cast<SceneNode*>(*c);
									override.data = variableNode->getFloatVars();
									assert(override.data);
									override.location = defaultValue->second.location;
									context.uniformOverrides.push_back(override);
								}
							}
						}
						
						pass.models.push_back(context);
					}
					
					break;
				}
			}
		}
	}
	
	// copy default uniforms back into the actual pass
	for (std::tr1::unordered_map <std::string, RenderPass::UniformData>::const_iterator i = uniforms.begin(); i != uniforms.end(); i++)
	{
		pass.defaultUniforms.push_back(i->second);
	}
	
	// copy default texture bindings back into the actual pass
	for (std::tr1::unordered_map <std::string, RenderPass::TextureData>::const_iterator i = textureBindings.begin(); i != textureBindings.end(); i++)
	{
		pass.defaultTextureBindings.push_back(i->second);
	}
}

void passAssignShader(const SceneNode & root, NodeTypes::NODETYPE type, keyed_container<RenderShader>::handle & out, 
					  const SceneNode * node, const std::string & shaderName, Renderer::SceneMapping & shaderMap)
{
	std::vector <const SceneNode *> nodes;
	root.getChildren(nodes, true, NodeTypes::getType(type), node);
	for (std::vector <const SceneNode *>::const_iterator i = nodes.begin(); i != nodes.end(); i++)
	{
		if ((*i)->getName() == shaderName)
		{
			Renderer::SceneMapping::iterator s = shaderMap.find(*i);
			if (s != shaderMap.end())
				out = s->second;
			else
			{
				out.invalidate();
			}
			
		}
	}
}

void Renderer::passSetState(RenderPass & pass, const SceneNode * node)
{
	pass.stateEnable.clear();
	pass.stateDisable.clear();
	pass.stateEnablei.clear();
	pass.stateDisablei.clear();
	pass.stateEnum.clear();
	
	std::vector <const SceneNode *> nodes;
	node->getChildren(nodes, false, NodeTypes::getType(NodeTypes::RENDER_STATE));
	if (nodes.empty())
	{
		logError("Pass "+node->getName()+" lacks a render state node, default/previous state will be used");
	}
	else 
	{
		if (nodes.size() > 1)
		{
			logError("Pass "+node->getName()+" has more than one render state node, only the first will be used");
		}
		
		const SceneNode * stateNode = nodes.front();
		assert(stateNode);
		const std::map <std::string, SceneNodeField> & fields = stateNode->getFields();
		
		for (std::map <std::string, SceneNodeField>::const_iterator i = fields.begin(); i != fields.end(); i++)
		{
			if (i->second.getType() == "bool")
			{
				bool value(false);
				stateNode->getField(i->first,value);
				GLenum glenum = GLEnumHelper.getEnum(i->first);
				if (value)
					pass.stateEnable.push_back(glenum);
				else
					pass.stateDisable.push_back(glenum);
			}
			else if (i->second.getType() == "booli" && !i->first.empty())
			{
				// we assume that the name is something like GL_ENUMi such as GL_BLEND3
				bool value(false);
				stateNode->getField(i->first,value);
				std::string enumstr = i->first.substr(0,i->first.length()-1);
				std::string numstr = i->first.substr(i->first.length()-1);
				std::stringstream conv(numstr);
				int num(-1);
				conv >> num;
				assert(num >= 0);
				GLenum glenum = GLEnumHelper.getEnum(enumstr);
				if (value)
				{
					pass.stateEnablei.push_back(std::make_pair(glenum,num));
				}
				else
				{
					pass.stateDisablei.push_back(std::make_pair(glenum,num));
				}
			}
			else
			{
				std::string value;
				stateNode->getField(i->first,value);
				if (!value.empty())
				{
					RenderPass::RenderState::RenderStateType statetype;
					if (getStateType(i->second.getType(), statetype))
					{
						RenderPass::RenderState state(i->first, statetype, value);
						pass.stateEnum.push_back(state);
					}
					else
					{
						logError("Unknown state type: "+i->second.getType());
					}
				}
			}
		}
	}
}

void Renderer::passLinkShaderProgramInternal(RenderPass & pass, const std::string & programName)
{
	glUseProgram(0); // make sure we don't have this program bound
	
	if (!pass.fragmentShader.valid() || !pass.vertexShader.valid())
	{
		if (pass.shaderProgram != 0)
		{
			glDeleteProgram(pass.shaderProgram);ERROR_CHECK;
			pass.shaderProgram = 0;
		}
		return;
	}
	
	const RenderShader & fragmentShader = shaders.get(pass.fragmentShader);
	const RenderShader & vertexShader = shaders.get(pass.vertexShader);
	
	if (fragmentShader.handle == 0 || vertexShader.handle == 0)
	{
		logError("Shader program "+programName+" can't be linked because the shaders have not yet been compiled");
	}
	else
	{
		if (pass.shaderProgram != 0)
		{
			glDeleteProgram(pass.shaderProgram);ERROR_CHECK;
		}
		pass.shaderProgram = glCreateProgram();ERROR_CHECK;
		glAttachShader(pass.shaderProgram, fragmentShader.handle);ERROR_CHECK;
		glAttachShader(pass.shaderProgram, vertexShader.handle);ERROR_CHECK;
		
		// make sure we get our vertex attributes bound to the proper names
		for (int i = 0; i < 8; i++)
		{
			assert(i < pass.shaderAttributeBindings.size());
			if (!pass.shaderAttributeBindings[i].empty())
			{
				glBindAttribLocation(pass.shaderProgram, i, pass.shaderAttributeBindings[i].c_str());ERROR_CHECK;
			}
		}
		
		// make sure output frag data locations are bound to the proper names
		int colorAttachmentCount = 0;
		for (int i = 0; i < pass.renderTargetAttachmentPoints.size(); i++)
		{
			if (pass.renderTargetAttachmentPoints[i] != GL_DEPTH_ATTACHMENT)
				colorAttachmentCount++;
		}
		if (colorAttachmentCount > 1) // my AMD 4850 drivers seem to have a bug that causes glBindFragDataLocation to screw up and ruin the attachments of other programs if i call glBindFragDataLocation on a program which has only one color attachment
		{
			for (int i = 0; i < pass.renderTargetVariables.size() && i < pass.renderTargetAttachmentPoints.size(); i++)
			{
				GLenum attachmentPoint = pass.renderTargetAttachmentPoints[i];
				if (attachmentPoint != GL_DEPTH_ATTACHMENT)
				{
					int colorNumber = attachmentPoint - GL_COLOR_ATTACHMENT0;
					std::string name = pass.renderTargetVariables[i];
					if (!name.empty())
					{
						glBindFragDataLocation(pass.shaderProgram, colorNumber, name.c_str());ERROR_CHECK;
					}
				}
			}
		}
		
		glLinkProgram(pass.shaderProgram);ERROR_CHECK;
		GLint linkStatus;
		glGetProgramiv(pass.shaderProgram, GL_LINK_STATUS, &linkStatus);
		if (!linkStatus)
		{
			GLint bufferSize(0);
			glGetProgramiv(pass.shaderProgram, GL_INFO_LOG_LENGTH, &bufferSize);
			GLchar infoLog[bufferSize+1];
			GLsizei infoLogLength;
			glGetProgramInfoLog(pass.shaderProgram, bufferSize, &infoLogLength, infoLog);
			infoLog[bufferSize] = '\0';
			logError("Linking of shader program "+programName+" failed\n"+infoLog);
			glDeleteProgram(pass.shaderProgram);ERROR_CHECK;
			pass.shaderProgram = 0;
		}
		else
		{
			logOutput("Successfully linked program "+programName);
			
			const bool verbose = false;
			if (verbose)
			{
				int activeAttributes = 0;
				glGetProgramiv(pass.shaderProgram, GL_ACTIVE_ATTRIBUTES, &activeAttributes);
				std::cout << "Program " << pass.shaderProgram << " active attributes: " << activeAttributes << std::endl;
				for (int i = 0; i < activeAttributes; i++)
				{
					const int bufSize = 1024;
					GLchar buffer[bufSize+1];
					GLsizei length;
					GLint size;
					GLenum type;
					glGetActiveAttrib(pass.shaderProgram, i, bufSize, &length, &size, &type, buffer);ERROR_CHECK;
					buffer[bufSize] = '\0';
					std::cout << "Program " << pass.shaderProgram << " attrib " << i << ": " << buffer << std::endl;
				}
				
				// verify frag data output locations
				for (int i = 0; i < pass.renderTargetVariables.size() && i < pass.renderTargetAttachmentPoints.size(); i++)
				{
					std::string name = pass.renderTargetVariables[i];
					GLint loc = glGetFragDataLocation(pass.shaderProgram, name.c_str()); ERROR_CHECK;
					if (verbose) std::cout << "Frag data name " << name << " is bound to location " << loc << std::endl;
				}
			}
		}
	}
}

void Renderer::passLinkShaderProgram(RenderPass & pass, const SceneNode * node, bool force)
{
	// look up the shader program binding, then look through other nodes in the tree
	// to find the shaders referred to
	std::string fragmentName, vertexName, programName;
	const SceneNode * programNode(NULL);
	bool haveShaderProgram(true);
	{
		std::vector <const SceneNode *> nodes;
		node->getChildren(nodes, false, NodeTypes::getType(NodeTypes::SHADER_PROGRAM));
		if (nodes.empty())
		{
			if (!doFastUpdate)
				logError("Pass "+node->getName()+" needs a shader program node");
			haveShaderProgram = false;
		}
		else
		{
			if (nodes.size() > 1)
			{
				if (!doFastUpdate)
					logError("Pass "+node->getName()+" has multiple shader program nodes; only one will be used");
			}
			programName = nodes.front()->getName();
			nodes.front()->getField("fragment shader", fragmentName);
			nodes.front()->getField("vertex shader", vertexName);
			pass.shaderAttributeBindings.resize(8);
			nodes.front()->getField("Attribute binding: position",pass.shaderAttributeBindings[0]);
			nodes.front()->getField("Attribute binding: normal",pass.shaderAttributeBindings[1]);
			nodes.front()->getField("Attribute binding: tangent",pass.shaderAttributeBindings[2]);
			nodes.front()->getField("Attribute binding: bitangent",pass.shaderAttributeBindings[3]);
			nodes.front()->getField("Attribute binding: color",pass.shaderAttributeBindings[4]);
			nodes.front()->getField("Attribute binding: uv0",pass.shaderAttributeBindings[5]);
			nodes.front()->getField("Attribute binding: uv1",pass.shaderAttributeBindings[6]);
			nodes.front()->getField("Attribute binding: uv2",pass.shaderAttributeBindings[7]);
			programNode = nodes.front();
		}
	}
	if (haveShaderProgram && programNode)
	{
		// get the fragment shader
		keyed_container<RenderShader>::handle newFragmentShader;
		passAssignShader(*sceneRoot, NodeTypes::FRAGMENT_SHADER, newFragmentShader, node, fragmentName, shaderMap);
		if (!newFragmentShader.valid() && !doFastUpdate)
			logError("Pass "+node->getName()+" shader program "+programName+" refers to missing fragment shader "+fragmentName);
		
		// get the vertex shader
		keyed_container<RenderShader>::handle newVertexShader;
		passAssignShader(*sceneRoot, NodeTypes::VERTEX_SHADER, newVertexShader, node, vertexName, shaderMap);
		if (!newVertexShader.valid() && !doFastUpdate)
			logError("Pass "+node->getName()+" shader program "+programName+" refers to missing vertex shader "+vertexName);
		
		// only proceed if something has changed
		if (newVertexShader != pass.vertexShader || newFragmentShader != pass.fragmentShader || 
			pass.shaderProgramFieldCache != programNode->getFields() ||
			force)
		{
			pass.shaderProgramFieldCache = programNode->getFields();
			pass.vertexShader = newVertexShader;
			pass.fragmentShader = newFragmentShader;
		
			// link the program
			if (pass.fragmentShader.valid() && pass.vertexShader.valid())
			{
				passLinkShaderProgramInternal(pass, programName);
			}
		}
	}
}

void Renderer::passSetFBO(RenderPass & pass, const SceneNode * node)
{
	// for now, we just delete and recreate the FBOs every time the scene is updated
	{
		if (pass.framebufferObject != 0)
			glDeleteFramebuffers(1, &pass.framebufferObject);
		pass.framebufferObject = 0;
		
		if (pass.renderbuffer != 0)
			glDeleteRenderbuffers(1, &pass.renderbuffer);
		pass.renderbuffer = 0;

		pass.renderTargets.clear();
		pass.renderTargetAttachmentPoints.clear();
		pass.renderTargetNames.clear();
		pass.renderTargetVariables.clear();
	}
	
	GLint maxAttachments;
	glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxAttachments);ERROR_CHECK;
	
	// handles are to the container "textures"
	std::vector <keyed_container_handle> colorTextures(maxAttachments);
	std::vector <keyed_container_handle> depthTextures(1);
	
	// a way to remember the render target texture names given the texture handle
	std::map <keyed_container_handle, std::string> renderTargetNames;
	
	// a way to remember the render target shader variable output name given the texture handle
	std::map <keyed_container_handle, std::string> renderTargetVariables;
	
	// find all color and depth render targets
	{
		std::vector <const SceneNode *> targetNodes;
		sceneRoot->getChildren(targetNodes, true, NodeTypes::getType(NodeTypes::RENDER_TARGET), node); // all render targets in the scene except those in other passes
		
		// put the render target nodes into a map for fast access
		typedef std::tr1::unordered_map <std::string, const SceneNode *> targetNodeMap_type;
		targetNodeMap_type targetNodeMap;
		for (std::vector <const SceneNode *>::const_iterator t = targetNodes.begin(); t != targetNodes.end(); t++)
		{
			// warn on duplicate names
			if (targetNodeMap.find((*t)->getName()) != targetNodeMap.end())
				logError("Multiple render target nodes with the same name: "+(*t)->getName()+", duplicates will be ignored");
			else
				targetNodeMap[(*t)->getName()] = *t;
		}

		std::vector <const SceneNode *> attachmentNodes;
		node->getChildren(attachmentNodes, false, NodeTypes::getType(NodeTypes::FRAMEBUFFER_ATTACHMENT)); // all framebuffer attachments under this pass
		
		// for my framebuffer attachments
		for (std::vector <const SceneNode *>::const_iterator a = attachmentNodes.begin(); a != attachmentNodes.end(); a++)
		{
			// find the render target
			std::string targetNodeStr;
			(*a)->getField("render target", targetNodeStr);
			if (!targetNodeStr.empty())
			{
				targetNodeMap_type::const_iterator it = targetNodeMap.find(targetNodeStr);
				if (it != targetNodeMap.end())
				{
					const SceneNode * const * t = &it->second;
					
					// add the render target node to the appropriate attachment vector
					std::string attachmentStr;
					(*a)->getField("attachment point", attachmentStr);
					if (attachmentStr.empty())
					{
						logError("Framebuffer attachment node "+(*a)->getName()+" doesn't have an attachment point set");
					}
					else
					{
						// get the textures keyed container handle for this render target node
						keyed_container_handle handle;
						SceneMapping::iterator h = textureMap.find(*t);
						if (h != textureMap.end())
						{
							handle = h->second;
						}
						
						if (!handle.valid())
						{
							logError("Framebuffer attachment node "+(*a)->getName()+" references render target node "+(*t)->getName()+" which is missing from the internal texture mapping");
						}
						else
						{
							GLenum attachment = GLEnumHelper.getEnum(attachmentStr);
							if (attachment == GL_DEPTH_ATTACHMENT)
							{
								if (depthTextures[0].valid())
									logError("Pass "+node->getName()+" has multiple framebuffer attachments set to "+attachmentStr+", duplicates will be ignored");
								else
								{
									depthTextures[0] = handle;
									renderTargetNames[handle] = targetNodeStr;
									renderTargetVariables[handle] = (*a)->getName();
								}
							}
							else
							{
								int attachmentId = attachment - GL_COLOR_ATTACHMENT0;
								assert(attachmentId >= 0);
								if (attachmentId >= maxAttachments)
								{
									logError("Framebuffer attachment node "+(*a)->getName()+" has color render target attachment point "+tostr(attachment)+" which is more than the "+tostr(maxAttachments)+" that this graphics card supports. It will be ignored.");
								}
								else
								{
									assert(attachmentId < colorTextures.size());
									if (colorTextures[attachmentId].valid())
										logError("Pass "+node->getName()+" has multiple framebuffer attachments set to "+attachmentStr+", duplicates will be ignored");
									else
									{
										colorTextures[attachmentId] = handle;
										renderTargetNames[handle] = targetNodeStr;
										renderTargetVariables[handle] = (*a)->getName();
									}
								}
							}
						}
					}
				}
				else
				{
					logError("Framebuffer attachment node "+(*a)->getName()+" refers to missing render target "+targetNodeStr);
				}
			}
		}
	}
	
	// find what resolution to use for the viewport resolution
	// also see if we have any attachments
	bool haveAttachments = false;
	{
		keyed_container_handle validHandle;
		if (depthTextures[0].valid())
			validHandle = depthTextures[0];
		else
		{
			// find the first valid color attachment
			for (std::vector <keyed_container_handle>::iterator i = colorTextures.begin(); i != colorTextures.end(); i++)
			{
				if (i->valid())
				{
					validHandle = *i;
					break;
				}
			}
		}
		
		// set the viewport resolution
		if (validHandle.valid())
		{
			haveAttachments = true;
			pass.width = textures.get(validHandle).width;
			pass.height = textures.get(validHandle).height;
		}
	}
	
	// if we have no attachments, we use the default framebuffer object
	bool pretendHaveNoAttachments = false;
	node->getField("render to default framebuffer (override attachments)", pretendHaveNoAttachments);
	if (!haveAttachments || pretendHaveNoAttachments)
	{
		pass.width = width;
		pass.height = height;
		return;
	}
	
	bool verbose = false;
	
	// generate the framebuffer
	glGenFramebuffers(1, &pass.framebufferObject);ERROR_CHECK;
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, pass.framebufferObject);ERROR_CHECK;
	if (verbose) std::cout << "Generated framebuffer object " << pass.framebufferObject << std::endl;
	if (verbose) std::cout << "glBindFramebuffer(GL_DRAW_FRAMEBUFFER, " << pass.framebufferObject << ");" << std::endl;
	
	// assign draw buffers
	std::vector <GLenum> drawBuffers(maxAttachments, GL_NONE);
	assert(colorTextures.size() == drawBuffers.size());
	if (verbose) std::cout << "Draw buffers: ";
	for (int i = 0; i < colorTextures.size(); i++)
	{
		if (colorTextures[i].valid())
			drawBuffers[i] = GL_COLOR_ATTACHMENT0+i;
		if (verbose)
		{
			std::cout << i << ":";
			if (drawBuffers[i] == GL_NONE)
				std::cout << "GL_NONE";
			else
				std::cout << "GL_COLOR_ATTACHMENT" << (drawBuffers[i]-GL_COLOR_ATTACHMENT0);
			std::cout << " ";
		}
	}
	if (verbose) std::cout << std::endl;
	
	if (verbose) std::cout << "Depth: " << (depthTextures[0].valid() ? "yes" : "no") << std::endl;
	
	GLint maxDrawBuffers;
	glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);ERROR_CHECK;
	if (verbose) std::cout << "glDrawBuffers(" << std::min(maxDrawBuffers,GLint(drawBuffers.size())) << ", ..." << std::endl;
	glDrawBuffers(std::min(maxDrawBuffers,GLint(drawBuffers.size())), &drawBuffers[0]);ERROR_CHECK;
	//glReadBuffer(GL_NONE);ERROR_CHECK;
	
	// attach color textures
	for (int i = 0; i < colorTextures.size(); i++)
	{
		if (colorTextures[i].valid())
		{
			const RenderTexture & tex = textures.get(colorTextures[i]);
			assert(tex.handle > 0); // we should have already created the texture
			GLint attachment = GL_COLOR_ATTACHMENT0+i;
			if (verbose) std::cout << "glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, " << GLEnumHelper.getEnum(attachment) << ", " << 
				GLEnumHelper.getEnum(tex.target) << ", " << tex.handle << ", 0);" << std::endl;
			glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, tex.target, tex.handle, 0);ERROR_CHECK;
			pass.renderTargets.push_back(colorTextures[i]);
			pass.renderTargetAttachmentPoints.push_back(attachment);
			pass.renderTargetNames.push_back(renderTargetNames.find(pass.renderTargets.back())->second);
			pass.renderTargetVariables.push_back(renderTargetVariables.find(pass.renderTargets.back())->second);
		}
	}
	
	// if we have no depth attachment, generate a depth renderbuffer
	if (!depthTextures[0].valid())
	{
		glGenRenderbuffers(1, &pass.renderbuffer);ERROR_CHECK;
		if (verbose) std::cout << "Generated renderbuffer " << pass.renderbuffer << std::endl;
		if (verbose) std::cout << "glBindRenderbuffer(GL_RENDERBUFFER, " << pass.renderbuffer << ");" << std::endl;
		glBindRenderbuffer(GL_RENDERBUFFER, pass.renderbuffer);ERROR_CHECK;
		if (verbose) std::cout << "glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, " << pass.width << ", " << pass.height << ");" << std::endl;
		glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, pass.width, pass.height);ERROR_CHECK;
		
		// attach the renderbuffer
		if (verbose) std::cout << "glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, " << pass.renderbuffer << ");" << std::endl;
		glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, pass.renderbuffer);ERROR_CHECK;
	}
	else
	{
		// attach the depth texture
		const RenderTexture & tex = textures.get(depthTextures[0]);
		assert(tex.handle); // we should have already created the texture
		if (verbose) std::cout << "glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, " << GLEnumHelper.getEnum(tex.target) << ", " << tex.handle << ", 0);" << std::endl;
		glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, tex.target, tex.handle, 0);ERROR_CHECK;
		
		pass.renderTargets.push_back(depthTextures[0]);
		pass.renderTargetAttachmentPoints.push_back(GL_DEPTH_ATTACHMENT);
		pass.renderTargetNames.push_back(renderTargetNames.find(pass.renderTargets.back())->second);
		pass.renderTargetVariables.push_back(renderTargetVariables.find(pass.renderTargets.back())->second);
	}
	
	// we should have a complete framebuffer object now
	GLenum status = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);ERROR_CHECK;
	if (status != GL_FRAMEBUFFER_COMPLETE)
	{
		logError("Unable to complete framebuffer for pass "+node->getName()+": "+GLEnumHelper.getEnum(status));
		
		if (verbose)
		{
			for (int i = 0; i < maxAttachments+1; i++)
			{
				GLenum attachment = GL_COLOR_ATTACHMENT0+i;
				if (i == maxAttachments)
					attachment = GL_DEPTH_ATTACHMENT;
				
				GLint type(GL_NONE), handle(0);
				glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, attachment, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &type);
				glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, attachment, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &handle);
				std::cout << GLEnumHelper.getEnum(attachment) << ": " << GLEnumHelper.getEnum(type) << " handle " << handle << std::endl;
			}
			
		}
		
		assert(!verbose); // can't read the errors otherwise
	}
	
	// assign user-defined varying out variables to fragment color numbers and relink the program
	/*if (pass.shaderProgram)
	{
		// make sure we don't have the program bound
		glUseProgram(0);ERROR_CHECK;
		
		// assign variables to color outputs
		for (int i = 0; i < colorTextures.size(); i++)
		{
			if (colorTextures[i].valid())
			{
				const RenderTexture & tex = textures.get(colorTextures[i]);
				assert(tex.handle > 0); // we should have already created the texture
				std::string name = renderTargetVariables[colorTextures[i]];
				if (!name.empty())
				{
					glBindFragDataLocation(pass.shaderProgram, i, name.c_str());ERROR_CHECK;
					if (verbose) std::cout << "Binding frag data location " << i << " to name " << name << std::endl;
				}
			}
		}
		
		// relink
		glLinkProgram(pass.shaderProgram);ERROR_CHECK;
		GLint linkStatus;
		glGetProgramiv(pass.shaderProgram, GL_LINK_STATUS, &linkStatus);
		if (!linkStatus)
		{
			GLint bufferSize(0);
			glGetProgramiv(pass.shaderProgram, GL_INFO_LOG_LENGTH, &bufferSize);
			GLchar infoLog[bufferSize+1];
			GLsizei infoLogLength;
			glGetProgramInfoLog(pass.shaderProgram, bufferSize, &infoLogLength, infoLog);
			infoLog[bufferSize] = '\0';
			logError(std::string("Relinking shader program failed\n")+infoLog);
		}
		
		// verify
		for (int i = 0; i < colorTextures.size(); i++)
		{
			if (colorTextures[i].valid())
			{
				const RenderTexture & tex = textures.get(colorTextures[i]);
				assert(tex.handle > 0); // we should have already created the texture
				std::string name = renderTargetVariables[colorTextures[i]];
				GLint loc = glGetFragDataLocation(pass.shaderProgram, name.c_str()); ERROR_CHECK;
				if (verbose) std::cout << "Frag data name " << name << " is bound to location " << loc << std::endl;
			}
		}
	}*/
	
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); ERROR_CHECK;
}

void Renderer::addPass(std::pair<RenderPass &, const SceneNode *> args)
{
	RenderPass & pass = args.first;
	const SceneNode * node = args.second;
	
	// set some default values since we're adding a node
	pass.shaderProgram = 0;
	
	updatePass(args);
}

void Renderer::updateConfiguration(const SceneNode & root, unsigned int newWidth, unsigned int newHeight, bool fast)
{
	// save some context for our Renderer::addPass callback to use
	sceneRoot = &root;
	width = newWidth;
	height = newHeight;
	doFastUpdate = fast;
	modelsOrTexturesTouched = false;
	
	if (!fast)
		logClear();
	
	// process all textures
	{
		std::vector <const SceneNode *> nodes;
		root.getChildren(nodes, true, "category:texture");
		std::tr1::function <void (RenderTexture&)> deleteFunction = std::tr1::bind(std::tr1::mem_fn(&Renderer::deleteTexture),this,std::tr1::placeholders::_1);
		std::tr1::function <void (std::pair<RenderTexture &, const SceneNode *>)> updateFunction = std::tr1::bind(std::tr1::mem_fn(&Renderer::updateTexture),this,std::tr1::placeholders::_1);
		std::tr1::function <void (std::pair<RenderTexture &, const SceneNode *>)> addFunction = std::tr1::bind(std::tr1::mem_fn(&Renderer::addTexture),this,std::tr1::placeholders::_1);
		handleUpdatedNodes(
			*this,
			&passes,
			nodes,
			textureMap,
			textures,
			deleteFunction,
			updateFunction,
			addFunction);
	}
	
	// process all models
	{
		std::vector <const SceneNode *> nodes;
		root.getChildren(nodes, true, NodeTypes::getType(NodeTypes::MODEL));
		std::tr1::function <void (RenderModel&)> deleteFunction = std::tr1::bind(std::tr1::mem_fn(&Renderer::deleteModel),this,std::tr1::placeholders::_1);
		std::tr1::function <void (std::pair<RenderModel &, const SceneNode *>)> updateFunction = std::tr1::bind(std::tr1::mem_fn(&Renderer::updateModel),this,std::tr1::placeholders::_1);
		std::tr1::function <void (std::pair<RenderModel &, const SceneNode *>)> addFunction = std::tr1::bind(std::tr1::mem_fn(&Renderer::addModel),this,std::tr1::placeholders::_1);
		handleUpdatedNodes(
			*this,
			&passes,
			nodes,
			modelMap,
			models,
			deleteFunction,
			updateFunction,
			addFunction);
	}
	
	// process all shaders
	{
		std::vector <const SceneNode *> nodes;
		root.getChildren(nodes, true, NodeTypes::getType(NodeTypes::FRAGMENT_SHADER));
		root.getChildren(nodes, true, NodeTypes::getType(NodeTypes::VERTEX_SHADER));
		std::tr1::function <void (RenderShader&)> deleteFunction = std::tr1::bind(std::tr1::mem_fn(&Renderer::deleteShader),this,std::tr1::placeholders::_1);
		std::tr1::function <void (std::pair<RenderShader &, const SceneNode *>)> updateFunction = std::tr1::bind(std::tr1::mem_fn(&Renderer::updateShader),this,std::tr1::placeholders::_1);
		std::tr1::function <void (std::pair<RenderShader &, const SceneNode *>)> addFunction = std::tr1::bind(std::tr1::mem_fn(&Renderer::addShader),this,std::tr1::placeholders::_1);
		handleUpdatedNodes(
			*this,
			&passes,
			nodes,
			shaderMap,
			shaders,
			deleteFunction,
			updateFunction,
			addFunction);
	}
	
	// process passes
	{
		std::vector <const SceneNode *> nodes;
		root.getChildren(nodes, false, NodeTypes::getType(NodeTypes::PASS));
		NodeSet addedPasses, deletedPasses;
		std::tr1::function <void (RenderPass&)> deleteFunction = std::tr1::bind(std::tr1::mem_fn(&Renderer::deletePass),this,std::tr1::placeholders::_1);
		std::tr1::function <void (std::pair<RenderPass &, const SceneNode *>)> updateFunction = std::tr1::bind(std::tr1::mem_fn(&Renderer::updatePass),this,std::tr1::placeholders::_1);
		std::tr1::function <void (std::pair<RenderPass &, const SceneNode *>)> addFunction = std::tr1::bind(std::tr1::mem_fn(&Renderer::addPass),this,std::tr1::placeholders::_1);
		handleUpdatedNodes(*this,
						   NULL,
						   nodes,
						   passMap,
						   passes,
						   deleteFunction,
						   updateFunction,
						   addFunction);
						   
		// reorder passes to match node order
		passOrder.clear();
		for (std::vector <const SceneNode *>::const_iterator i = nodes.begin(); i != nodes.end(); i++)
		{
			passOrder.push_back(&passes.get(passMap.find(*i)->second));
		}
	}
	
	// relink shaders
	/*{
		std::vector <const SceneNode *> nodes;
		root.getChildren(nodes, false, NodeTypes::getType(NodeTypes::PASS));
		for (std::vector <const SceneNode *>::iterator i = nodes.begin(); i != nodes.end(); i++)
		{
			if ((*i)->getName() == "track opaque")
			{
				RenderPass & pass = passes.get(passMap.find(*i)->second);
				passLinkShaderProgram(pass, *i, true);
			}
		}
	}*/
	
	// rebind VAOs
	rebindModelVaos();
}

void Renderer::applyUniform(GLint location, const std::tr1::shared_ptr <std::vector <float> > data)
{
	switch (data->size())
	{
		case 1:
		glUniform1f(location, (*data)[0]);
		break;
		
		case 2:
		glUniform2f(location, (*data)[0], (*data)[1]);
		break;
		
		case 3:
		glUniform3f(location, (*data)[0], (*data)[1], (*data)[2]);
		break;
		
		case 4:
		glUniform4f(location, (*data)[0], (*data)[1], (*data)[2], (*data)[3]);
		break;
		
		case 16:
		glUniformMatrix4fv(location, 1, false, &(*data)[0]);
		break;
		
		default:
		std::cerr << "Encountered unexpected uniform size: " << data->size() << " location " << location << std::endl;
        assert(0);
	};

    if (ERROR_CHECK)
    {
        std::stringstream errtxt;
        errtxt << "uniform location was " << location << ", data was size " << data->size();
        logError(errtxt.str());
    }
}

void printVector(const std::vector <float> & vec, std::ostream & out)
{
	for (int i = 0; i < vec.size(); i++)
	{
		if (i != 0)
			out << ", ";
		out << vec[i];
	}
}

void Renderer::applyRenderState(RenderPass::RenderState val)
{
	static GLenum last_blendequation_rgb = GL_FUNC_ADD;
	static GLenum last_blendequation_alpha = GL_FUNC_ADD;
	static GLenum last_blend_src_rgb = GL_ONE;
	static GLenum last_blend_src_alpha = GL_ONE;
	static GLenum last_blend_dst_rgb = GL_ZERO;
	static GLenum last_blend_dst_alpha = GL_ZERO;
	
	switch (val.pname)
	{
		case GL_DEPTH_FUNC:
		glDepthFunc(val.param[0]); ERROR_CHECK;
		break;
		
		case GL_DEPTH_WRITEMASK:
		glDepthMask(val.param[0]); ERROR_CHECK;
		break;
		
		case GL_CULL_FACE_MODE:
		glCullFace(val.param[0]); ERROR_CHECK;
		break;
		
		case GL_FRONT_FACE:
		glFrontFace(val.param[0]); ERROR_CHECK;
		break;
		
		case GL_POLYGON_MODE:
		glPolygonMode(GL_FRONT_AND_BACK, val.param[0]); ERROR_CHECK;
		break;
		
		case GL_POLYGON_OFFSET_FACTOR:
		glPolygonOffset(val.fparam[0], val.fparam[1]); ERROR_CHECK;
		break;
		
		case GL_SAMPLE_COVERAGE_VALUE:
		glSampleCoverage(val.fparam[0], (val.fparam[1] != 0)); ERROR_CHECK;
		break;
		
		case GL_SAMPLE_MASK_VALUE:
		if (glSampleMaski)
			glSampleMaski(0, val.param[0]); ERROR_CHECK;
		break;
		
		case GL_LINE_SMOOTH_HINT:
		case GL_POLYGON_SMOOTH_HINT:
		case GL_FRAGMENT_SHADER_DERIVATIVE_HINT:
		glHint(val.pname, val.param[0]); ERROR_CHECK;
		break;
		
		case GL_BLEND_EQUATION_RGB:
		last_blendequation_rgb = val.param[0];
		glBlendEquationSeparate(last_blendequation_rgb, last_blendequation_alpha); ERROR_CHECK;
		break;
		
		case GL_BLEND_EQUATION_ALPHA:
		last_blendequation_alpha = val.param[0];
		glBlendEquationSeparate(last_blendequation_rgb, last_blendequation_alpha); ERROR_CHECK;
		break;
		
		case GL_BLEND_SRC_RGB:
		last_blend_src_rgb = val.param[0];
		glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha); ERROR_CHECK;
		break;
		
		case GL_BLEND_SRC_ALPHA:
		last_blend_src_alpha = val.param[0];
		glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha); ERROR_CHECK;
		break;
		
		case GL_BLEND_DST_RGB:
		last_blend_dst_rgb = val.param[0];
		glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha); ERROR_CHECK;
		break;
		
		case GL_BLEND_DST_ALPHA:
		last_blend_dst_alpha = val.param[0];
		glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha); ERROR_CHECK;
		break;
		
		default:
		std::cerr << "Don't know how to apply enum state variable: " << GLEnumHelper.getEnum(val.pname) << std::endl;
		break;
	}
}

void applySamplerState(GLint target, const RenderPass::Sampler & sampler)
{
	for (std::vector <RenderPass::RenderState>::const_iterator s = sampler.state.begin(); s != sampler.state.end(); s++)
	{
		switch (s->type)
		{
			case RenderPass::RenderState::SS_ENUM:
			case RenderPass::RenderState::SS_INT:
			glTexParameteri(target, s->pname, s->param[0]);
			break;
			
			/*case RenderPass::RenderState::SS_ENUM4:
			glTexParameteriv(target, s->pname, s->param);
			break;*/
			
			case RenderPass::RenderState::SS_FLOAT:
			glTexParameterf(target, s->pname, s->fparam[0]);
			break;
			
			case RenderPass::RenderState::SS_FLOAT4:
			glTexParameterfv(target, s->pname, s->fparam);
			break;
			
			default:
			assert(!"Invalid sampler state type");
		}
	}
}

void Renderer::applyTextureBind(const RenderPass::TextureData & textureData, const std::vector <RenderPass::Sampler> & samplers, bool verbose = false)
{
	GLint tu = textureData.tu;
	glActiveTexture(GL_TEXTURE0+tu);ERROR_CHECK;
	GLuint textureHandle = 0;
	
	RenderTexture & texture = textures.get(textureData.texture);
    if (texture.handle != 0)
    {
        glBindTexture(texture.target, texture.handle);
        if (ERROR_CHECK)
        {
            std::stringstream errtxt;
            errtxt << "texture path \"" << texture.path << "\", node name \"" << texture.nodeName << "\", target was " << texture.target << ", handle was " << texture.handle;
            logError(errtxt.str());
        }
        textureHandle = texture.handle;

        // apply sampler state
        assert(tu < samplers.size());
        applySamplerState(texture.target, samplers[tu]);

        if (verbose) std::cout << "tu " << tu << ": " << textureHandle << std::endl;
    }
}

struct Dummy
{
	int operator()() const {return 0;}
};

void Renderer::draw()
{
	const std::vector <ExternalModelContext <Dummy,Dummy> > dummy;
	drawWithExternals<Dummy,Dummy>(dummy);
}

std::vector <std::string> childNodeNames(const SceneNode & node, const std::string & type)
{
	std::vector <const SceneNode *> children;
	node.getChildren(children, true, type);
	std::vector <std::string> names(children.size());
	for (int i = 0; i < children.size(); i++)
		names[i] = children[i]->getName();
	return names;
}

std::vector <std::string> getEnums(const std::vector <GLenum> & enums)
{
	std::vector <std::string> strings(enums.size());
	for (int i = 0; i < enums.size(); i++)
		strings[i] = GLEnumHelper.getEnum(enums[i]);
	return strings;
}

std::vector <std::pair <std::string, int> > getEnumsi(const std::vector <std::pair <GLenum, unsigned int> > & enums)
{
	std::vector <std::pair <std::string, int> > strings(enums.size());
	for (int i = 0; i < enums.size(); i++)
	{
		strings[i] = std::make_pair(GLEnumHelper.getEnum(enums[i].first),enums[i].second);
	}
	return strings;
}

std::pair <std::string, RealtimeExportPassInfo::RenderState> convertRenderState(RenderPass::RenderState s, std::ostream & log)
{
	RealtimeExportPassInfo::RenderState rs;
	switch (s.type)
	{
		case RenderPass::RenderState::SS_ENUM:
		rs.type = "enum";
		rs.enumdata = GLEnumHelper.getEnum(s.param[0]);
		break;
		
		case RenderPass::RenderState::SS_FLOAT:
		rs.type = "float";
		rs.floatdata.push_back(s.fparam[0]);
		break;
		
		case RenderPass::RenderState::SS_FLOAT2:
		rs.type = "float";
		rs.floatdata.push_back(s.fparam[0]);
		rs.floatdata.push_back(s.fparam[1]);
		break;
		
		case RenderPass::RenderState::SS_FLOAT4:
		rs.type = "float";
		rs.floatdata.push_back(s.fparam[0]);
		rs.floatdata.push_back(s.fparam[1]);
		rs.floatdata.push_back(s.fparam[2]);
		rs.floatdata.push_back(s.fparam[3]);
		break;
		
		case RenderPass::RenderState::SS_INT:
		rs.type = "int";
		rs.intdata.push_back(s.param[0]);
		break;
		
		default:
		log << "unhandled renderstate type enum: " << s.type << std::endl;
	}
	return std::make_pair(GLEnumHelper.getEnum(s.pname), rs);
}

RealtimeExportPassInfo RenderPass::exportRealtime(const SceneNode & passNode, const keyed_container <RenderShader> & shaders, const keyed_container <RenderTexture> & textures, std::ostream & log) const
{
	RealtimeExportPassInfo i;
	
	i.name = passNode.getName();
	
	// user-defined fields
	i.userDefinedFields.clear();
	bool stillHaveUserFields = true;
	for (int n = 1; stillHaveUserFields; n++)
	{
		std::stringstream fieldStream;
		fieldStream << "user defined field " << n;
		std::string fieldName = fieldStream.str() + " name";
		std::string fieldValue = fieldStream.str() + " value";
		std::string name, value;
		stillHaveUserFields = passNode.getField(fieldName, name);
		if (stillHaveUserFields)
		{
			stillHaveUserFields = passNode.getField(fieldValue, value);
			
			if (stillHaveUserFields)
			{
				i.userDefinedFields[name] = value;
			}
		}
	}
	
	// draw groups
	std::vector <const SceneNode *> nodes;
	passNode.getChildren(nodes, false, NodeTypes::getType(NodeTypes::SCENE));
	for (std::vector <const SceneNode *>::const_iterator n = nodes.begin(); n != nodes.end(); n++)
	{
		std::string targetGroup;
		(*n)->getField("draw group", targetGroup);
		i.drawGroups.push_back(targetGroup);
	}
	
	// misc
	i.clearColor = clearMask & GL_COLOR_BUFFER_BIT;
	i.clearDepth = clearMask & GL_DEPTH_BUFFER_BIT;
	i.clearStencil = clearMask & GL_STENCIL_BUFFER_BIT;
	i.clearColorValue = std::vector <float>(&clearColor[0], &clearColor[4]);
	i.clearDepthValue = clearDepth;
	i.clearStencilValue = clearStencil;
	i.shaderAttributeBindings = shaderAttributeBindings;
	
	// shaders
	if (!vertexShader.valid())
		log << "Found a pass with an empty vertex shader" << std::endl;
	else
	{
		i.vertexShader = stripPath(shaders.find(vertexShader)->path);
		i.vertexShaderDefines = shaders.find(vertexShader)->defines;
	}
	if (!fragmentShader.valid())
		log << "Found a pass with an empty fragment shader" << std::endl;
	else
	{
		i.fragmentShader = stripPath(shaders.find(fragmentShader)->path);
		i.fragmentShaderDefines = shaders.find(fragmentShader)->defines;
	}
	
	// uniforms
	for (std::tr1::unordered_map <std::string, UniformData>::const_iterator u = variableNameToDefaultValue.begin(); u != variableNameToDefaultValue.end(); u++)
	{
		std::string name = u->first;
        i.uniforms[name].data = *u->second.data;
	}
	
	// states
	i.stateEnable = getEnums(stateEnable);
	i.stateDisable = getEnums(stateDisable);
	i.stateEnablei = getEnumsi(stateEnablei);
	i.stateDisablei = getEnumsi(stateDisablei);
	for (std::vector <RenderState>::const_iterator s = stateEnum.begin(); s != stateEnum.end(); s++)
	{
		i.stateEnum.insert(convertRenderState(*s,log));
	}
	
	// render targets
	if (renderTargets.size() != renderTargetAttachmentPoints.size())
		log << "renderTargets size does not match renderTargetAttachmentPoints size" << std::endl;
	for (int n = 0; n < renderTargets.size() && n < renderTargetAttachmentPoints.size(); n++)
	{
		RealtimeExportPassInfo::RenderTargetInfo rt;
		const RenderTexture & tex = *textures.find(renderTargets[n]);
		rt.name = renderTargetNames[n];
		rt.variable = renderTargetVariables[n];
		rt.format = GLEnumHelper.getEnum(tex.internalFormat);
		rt.target = GLEnumHelper.getEnum(tex.target);
		rt.autoMipmap = tex.autoMipmap;
		rt.width = tex.origw;
		rt.height = tex.origh;
		rt.widthHeightAreMultiples = tex.origMultiples;
		i.renderTargets.insert(std::make_pair(GLEnumHelper.getEnum(renderTargetAttachmentPoints[n]), rt));
	}
	
	// samplers
	for (int s = 0; s < samplers.size(); s++)
	{
		std::string textureName;
		for (std::tr1::unordered_map <std::string, GLint>::const_iterator t = textureNameToTextureUnit.begin(); t != textureNameToTextureUnit.end(); t++)
		{
			if (t->second == s)
				textureName = t->first;
		}
		
		std::string name = samplers[s].name;
		
		assert(!textureName.empty());
		
		RealtimeExportPassInfo::Sampler & sampler = i.samplers[name];
		
		sampler.textureName = textureName;
		
		for (int r = 0; r < samplers[s].state.size(); r++)
		{
			sampler.state.insert(convertRenderState(samplers[s].state[r],log));
		}
	}
	
	return i;
}

bool Renderer::exportRealtime(const SceneNode & root, const std::string & file, std::ostream & log)
{
	std::ofstream f(file.c_str());
	if (!f)
		return false;
	
	joeserialize::TextOutputSerializer out(f);
	joeserialize::Serializer & s = out;
	
	// we need to associate renderpass pointers with nodes
	std::map <RenderPass*,const SceneNode*> passToNode;
	{
		std::vector <const SceneNode *> nodes;
		root.getChildren(nodes, false, NodeTypes::getType(NodeTypes::PASS));
		for (std::vector <const SceneNode *>::iterator n = nodes.begin(); n != nodes.end(); n++)
		{
			SceneMapping::iterator sm = passMap.find(*n);
			if (sm != passMap.end())
			{
				passToNode[&*passes.find(sm->second)] = *n;
			}
		}
	}
	
	std::vector <RealtimeExportPassInfo> passinfos;
	int count = 0;
	for (std::vector <RenderPass*>::iterator pi = passOrder.begin(); pi != passOrder.end(); pi++,count++)
	{
		RenderPass * p = *pi;
		std::map <RenderPass*,const SceneNode*>::iterator ptn = passToNode.find(p);
		if (ptn == passToNode.end())
			log << "Found a RenderPass without a SceneNode entry; it will be ignored" << std::endl;
		else
			passinfos.push_back(p->exportRealtime(*ptn->second,shaders,textures,log));
	}
	
	if (!s.Serialize("passList", passinfos))
		return false;
	else
		return true;
}

template <typename external_texture_apply_functor, typename external_model_apply_functor>
void Renderer::drawWithExternals(const std::vector <ExternalModelContext <external_texture_apply_functor, external_model_apply_functor> > & externalModels)
{
	const bool verbose = false;
	logToWindow = false;
	
	int count = 0;
	//for (keyed_container <RenderPass>::const_iterator p = passes.begin(); p != passes.end(); p++,count++)
	for (std::vector <RenderPass*>::iterator pi = passOrder.begin(); pi != passOrder.end(); pi++,count++)
	{
		RenderPass * p = *pi;
		
		if (p->enable)
		{
			if (verbose) std::cout << "pass " << count << std::endl;
			
			// bind the framebuffer and set the viewport
			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, p->framebufferObject);ERROR_CHECK;
			GLenum status = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);ERROR_CHECK;
			if (status != GL_FRAMEBUFFER_COMPLETE)
			{
				std::cerr << "Incomplete framebuffer when drawing pass "+tostr(count)+": "+GLEnumHelper.getEnum(status) << std::endl;
			}
			else
			{
				glViewport(0,0,p->width,p->height);ERROR_CHECK;
				
				if (p->shaderProgram != 0)
				{
					if (verbose) std::cout << "program " << p->shaderProgram << std::endl;
					
					glUseProgram(p->shaderProgram);ERROR_CHECK;
					
					// setup state
					for (std::vector <GLenum>::const_iterator s = p->stateEnable.begin(); s != p->stateEnable.end(); s++)
					{
						glEnable(*s); glGetError(); //discard errors
						if (verbose) std::cout << GLEnumHelper.getEnum(*s) << ": enabled" << std::endl;
					}
					for (std::vector <GLenum>::const_iterator s = p->stateDisable.begin(); s != p->stateDisable.end(); s++)
					{
						glDisable(*s); glGetError(); //discard errors
						if (verbose) std::cout << GLEnumHelper.getEnum(*s) << ": disabled" << std::endl;
					}
					for (std::vector <std::pair <GLenum,unsigned int> >::const_iterator s = p->stateEnablei.begin(); s != p->stateEnablei.end(); s++)
					{
						wrapper_glEnablei(s->first, s->second); glGetError(); //discard errors
						if (verbose) std::cout << GLEnumHelper.getEnum(s->first) << ": enabled for MRT " << s->second << std::endl;
					}
					for (std::vector <std::pair <GLenum,unsigned int> >::const_iterator s = p->stateDisablei.begin(); s != p->stateDisablei.end(); s++)
					{
						wrapper_glDisablei(s->first, s->second); glGetError(); //discard errors
						if (verbose) std::cout << GLEnumHelper.getEnum(s->first) << ": disabled for MRT " << s->second << std::endl;
					}
					for (std::vector <RenderPass::RenderState>::const_iterator s = p->stateEnum.begin(); s != p->stateEnum.end(); s++)
					{
						applyRenderState(*s);
						//if (verbose) std::cout << GLEnumHelper.getEnum(s->pname) << ": " << GLEnumHelper.getEnum(s->param[0]) << std::endl;
					}
					
					glClearColor(p->clearColor[0],p->clearColor[1],p->clearColor[2],p->clearColor[3]);
					glClearDepth(p->clearDepth);
					glClearStencil(p->clearStencil);
					glClear(p->clearMask);ERROR_CHECK;
					
					// setup default uniforms
					if (verbose) std::cout << "uniforms: " << p->defaultUniforms.size() << std::endl;
					for (std::vector <RenderPass::UniformData>::const_iterator u = p->defaultUniforms.begin(); u != p->defaultUniforms.end(); u++)
					{
						if (verbose)
						{
							std::cout << "uniform " << u->location << ": ";
							printVector(*u->data, std::cout);
							std::cout << std::endl;
						}
						applyUniform(u->location, u->data);
					}
					
					// setup default textures
					if (verbose) std::cout << "default textures: " << p->defaultTextureBindings.size() << std::endl;
					for (std::vector <RenderPass::TextureData>::const_iterator t = p->defaultTextureBindings.begin(); t != p->defaultTextureBindings.end(); t++)
					{
						applyTextureBind(*t, p->samplers, verbose);
					}
					
					// for each model
					for (std::vector <RenderPass::ModelContext>::const_iterator m = p->models.begin(); m != p->models.end(); m++)
					{
						RenderModel model = models.get(m->model);
						if (model.vao != 0)
						{
							if (verbose) std::cout << "model " << model.vao << ": " << model.elementCount << std::endl;
							
							// bind all of the texture overrides
							// remember which we overrode so we can restore them later
							std::tr1::unordered_set <GLint> overriddenTUs;
                            if (verbose) std::cout << "override textures: " << m->textureBindingOverrides.size() << std::endl;
							for (std::vector <RenderPass::TextureData>::const_iterator t = m->textureBindingOverrides.begin(); t != m->textureBindingOverrides.end(); t++)
							{
								applyTextureBind(*t, p->samplers, verbose);
								overriddenTUs.insert(t->tu);
							}
							
							// apply any uniform overrides
							// remember which we overrode so we can restore them later
							std::tr1::unordered_set <GLint> overriddenLocations;
							for (std::vector <RenderPass::UniformData>::const_iterator u = m->uniformOverrides.begin(); u != m->uniformOverrides.end(); u++)
							{
								overriddenLocations.insert(u->location);
								applyUniform(u->location, u->data);
							}
							
							// draw the geometry
							glBindVertexArray(model.vao);ERROR_CHECK;
							glDrawElements(GL_TRIANGLES, model.elementCount, GL_UNSIGNED_INT, 0);ERROR_CHECK;
							
							// restore uniform variable defaults
							for (std::vector <RenderPass::UniformData>::const_iterator u = p->defaultUniforms.begin(); u != p->defaultUniforms.end(); u++)
							{
								if (overriddenLocations.find(u->location) != overriddenLocations.end())
									applyUniform(u->location, u->data);
							}
							
							// restore texture bind defaults
							for (std::vector <RenderPass::TextureData>::const_iterator t = p->defaultTextureBindings.begin(); t != p->defaultTextureBindings.end(); t++)
							{
								if (overriddenTUs.find(t->tu) != overriddenTUs.end())
									applyTextureBind(*t, p->samplers, verbose);
							}
						}
					}
					
					// draw external models
					{
						int externalCount = 0;
						for (typename std::vector <ExternalModelContext <external_texture_apply_functor, external_model_apply_functor> >::const_iterator m = externalModels.begin(); m != externalModels.end(); m++,externalCount++)
						{
							if (verbose) std::cout << "external model " << externalCount << std::endl;
							
							// bind all of the texture overrides
							// remember which we overrode so we can restore them later
							std::tr1::unordered_set <GLint> overriddenTUs;
							for (typename std::vector <typename ExternalModelContext<external_texture_apply_functor, external_model_apply_functor>::ExternalTextureData>::const_iterator t = m->textureBindingOverrides.begin(); t != m->textureBindingOverrides.end(); t++)
							{
								std::tr1::unordered_map <std::string, GLint>::const_iterator tuLookup = p->textureNameToTextureUnit.find(t->name);
								if (tuLookup != p->textureNameToTextureUnit.end())
								{
									GLint tu = tuLookup->second;
									glActiveTexture(GL_TEXTURE0+tu);ERROR_CHECK;
									
									// this external functor should bind the texture
									GLint textureTarget = t->textureApply();ERROR_CHECK;
									
									// apply sampler state
									assert(tu < p->samplers.size());
									applySamplerState(textureTarget, p->samplers[tu]);
									
									if (verbose) std::cout << "texture " << t->name << ": tu " << tu << ", target " << textureTarget << std::endl;
									
									overriddenTUs.insert(tu);
								}
								else
								{
									if (verbose) std::cout << "texture " << t->name << " not bound to a TU this pass" << std::endl;
								}
							}
							
							// apply any uniform overrides
							// remember which we overrode so we can restore them later
							std::tr1::unordered_set <GLint> overriddenLocations;
							for (typename std::vector <typename ExternalModelContext<external_texture_apply_functor, external_model_apply_functor>::ExternalUniformData>::const_iterator u = m->uniformOverrides.begin(); u != m->uniformOverrides.end(); u++)
							{
								std::tr1::unordered_map <std::string, GLint>::const_iterator locLookup = p->variableNameToUniformLocation.find(u->name);
								if (locLookup != p->variableNameToUniformLocation.end())
								{
									GLint location = locLookup->second;
									overriddenLocations.insert(location);
									applyUniform(location, u->data);
								}
								else
								{
									if (verbose) std::cout << "uniform " << u->name << " not bound to a shader variable this pass" << std::endl;
								}
							}
							
							// this external functor should draw the geometry
							m->modelApply();ERROR_CHECK;
							
							// restore uniform variable defaults
							for (std::vector <RenderPass::UniformData>::const_iterator u = p->defaultUniforms.begin(); u != p->defaultUniforms.end(); u++)
							{
								if (overriddenLocations.find(u->location) != overriddenLocations.end())
									applyUniform(u->location, u->data);
							}
							
							// restore texture bind defaults
							for (std::vector <RenderPass::TextureData>::const_iterator t = p->defaultTextureBindings.begin(); t != p->defaultTextureBindings.end(); t++)
							{
								if (overriddenTUs.find(t->tu) != overriddenTUs.end())
									applyTextureBind(*t, p->samplers, verbose);
							}
						}
					}
				}
				
				// unbind the framebuffer and build mipmaps for render targets that have autoMipmap set
				glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);ERROR_CHECK;
				for (std::vector <keyed_container<RenderTexture>::handle>::const_iterator t = p->renderTargets.begin(); t != p->renderTargets.end(); t++)
				{
					const RenderTexture & tex = textures.get(*t);
					if (tex.autoMipmap)
					{
						glBindTexture(tex.target, tex.handle);ERROR_CHECK;
						glGenerateMipmap(tex.target);ERROR_CHECK;
						glBindTexture(tex.target, 0);ERROR_CHECK;
					}
				}
			}
		}
	}
	
	logToWindow = true;
}

template void Renderer::drawWithExternals<Dummy,Dummy>(const std::vector <ExternalModelContext <Dummy,Dummy> > & externalModels);

